diff --git a/configure.ac b/configure.ac index 91fd12040..a9533aa4d 100644 --- a/configure.ac +++ b/configure.ac @@ -75,7 +75,8 @@ PKG_CHECK_MODULES(MUTTER_PLUGIN, gio-2.0 >= $GIO_MIN_VERSION clutter-x11-1.0 >= $CLUTTER_MIN_VERSION clutter-glx-1.0 >= $CLUTTER_MIN_VERSION libstartup-notification-1.0 - gobject-introspection-1.0 >= $GOBJECT_INTROSPECTION_MIN_VERSION) + gobject-introspection-1.0 >= $GOBJECT_INTROSPECTION_MIN_VERSION + libcanberra) saved_CFLAGS=$CFLAGS saved_LIBS=$LIBS @@ -90,6 +91,7 @@ PKG_CHECK_MODULES(TIDY, clutter-1.0) PKG_CHECK_MODULES(ST, clutter-1.0 gtk+-3.0 libcroco-0.6 gnome-desktop-3.0 >= 2.90.0) PKG_CHECK_MODULES(GDMUSER, dbus-glib-1 gtk+-3.0) PKG_CHECK_MODULES(TRAY, gtk+-3.0) +PKG_CHECK_MODULES(GVC, libpulse libpulse-mainloop-glib gobject-2.0) MUTTER_BIN_DIR=`$PKG_CONFIG --variable=exec_prefix mutter-plugins`/bin # FIXME: metacity-plugins.pc should point directly to its .gir file diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index ab735482f..e8b26daec 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -235,10 +235,6 @@ StTooltip { spacing: 4px; } -#statusTray { - spacing: 14px; -} - #legacyTray { spacing: 14px; padding-left: 14px; @@ -903,9 +899,19 @@ StTooltip { padding-bottom: 8px; } -#summary-notification-bin #notification { - /* message-tray.height + notification.padding-bottom */ - padding-bottom: 44px; +.summary-notification-boxpointer { + -arrow-border-radius: 9px; + -arrow-background-color: rgba(0,0,0,0.9); + -arrow-border-width: 2px; + -arrow-border-color: #5f5f5f; + -arrow-base: 30px; + -arrow-rise: 15px; +} + +.summary-notification-boxpointer #notification { + border-radius: 9px; + background: rgba(0,0,0,0) !important; + padding-bottom: 12px; } #notification-scrollview { diff --git a/js/Makefile.am b/js/Makefile.am index 199321ce0..86414e631 100644 --- a/js/Makefile.am +++ b/js/Makefile.am @@ -44,6 +44,7 @@ nobase_dist_js_DATA = \ ui/statusIconDispatcher.js \ ui/statusMenu.js \ ui/status/accessibility.js \ + ui/status/volume.js \ ui/telepathyClient.js \ ui/tweener.js \ ui/windowAttentionHandler.js \ diff --git a/js/ui/boxpointer.js b/js/ui/boxpointer.js index d6bd0d0c8..916005a9f 100644 --- a/js/ui/boxpointer.js +++ b/js/ui/boxpointer.js @@ -5,6 +5,10 @@ const Lang = imports.lang; const St = imports.gi.St; const Shell = imports.gi.Shell; +const Tweener = imports.ui.tweener; + +const POPUP_ANIMATION_TIME = 0.15; + /** * BoxPointer: * @side: A St.Side type; currently only St.Side.TOP is implemented @@ -38,6 +42,80 @@ BoxPointer.prototype = { this.bin.raise(this._border); }, + animateAppear: function(onComplete) { + let x = this.actor.x; + let y = this.actor.y; + let themeNode = this.actor.get_theme_node(); + let [found, rise] = themeNode.get_length('-arrow-rise', false); + if (!found) + rise = 0; + + this.actor.opacity = 0; + this.actor.show(); + + switch (this._arrowSide) { + case St.Side.TOP: + this.actor.y -= rise; + break; + case St.Side.BOTTOM: + this.actor.y += rise; + break; + case St.Side.LEFT: + this.actor.x -= rise; + break; + case St.Side.RIGHT: + this.actor.x += rise; + break; + } + + Tweener.addTween(this.actor, { opacity: 255, + x: x, + y: y, + transition: "linear", + onComplete: onComplete, + time: POPUP_ANIMATION_TIME }); + }, + + animateDisappear: function(onComplete) { + let x = this.actor.x; + let y = this.actor.y; + let originalX = this.actor.x; + let originalY = this.actor.y; + let themeNode = this.actor.get_theme_node(); + let [found, rise] = themeNode.get_length('-arrow-rise', false); + if (!found) + rise = 0; + + switch (this._arrowSide) { + case St.Side.TOP: + y += rise; + break; + case St.Side.BOTTOM: + y -= rise; + break; + case St.Side.LEFT: + x += rise; + break; + case St.Side.RIGHT: + x -= rise; + break; + } + + Tweener.addTween(this.actor, { opacity: 0, + x: x, + y: y, + transition: "linear", + time: POPUP_ANIMATION_TIME, + onComplete: Lang.bind(this, function () { + this.actor.hide(); + this.actor.x = originalX; + this.actor.y = originalY; + if (onComplete) + onComplete(); + }) + }); + }, + _adjustAllocationForArrow: function(isWidth, alloc) { let themeNode = this.actor.get_theme_node(); let found, borderWidth, base, rise; @@ -186,6 +264,92 @@ BoxPointer.prototype = { cr.stroke(); }, + setPosition: function(sourceActor, gap, alignment) { + let primary = global.get_primary_monitor(); + + // We need to show it now to force an allocation, + // so that we can query the correct size. + this.actor.show(); + + // Position correctly relative to the sourceActor + let [sourceX, sourceY] = sourceActor.get_transformed_position(); + let [sourceWidth, sourceHeight] = sourceActor.get_transformed_size(); + + let [minWidth, minHeight, natWidth, natHeight] = this.actor.get_preferred_size(); + + let resX, resY; + + switch (this._arrowSide) { + case St.Side.TOP: + resY = sourceY + sourceHeight + gap; + break; + case St.Side.BOTTOM: + resY = sourceY - natHeight - gap; + break; + case St.Side.LEFT: + resX = sourceX + sourceWidth + gap; + break; + case St.Side.RIGHT: + resX = sourceX - natWidth - gap; + break; + } + + // Now align and position the pointing axis, making sure + // it fits on screen + switch (this._arrowSide) { + case St.Side.TOP: + case St.Side.BOTTOM: + switch (alignment) { + case St.Align.START: + resX = sourceX; + break; + case St.Align.MIDDLE: + resX = sourceX - Math.floor((natWidth - sourceWidth) / 2); + break; + case St.Align.END: + resX = sourceX - (natWidth - sourceWidth); + break; + } + + resX = Math.min(resX, primary.x + primary.width - natWidth); + resX = Math.max(resX, primary.x); + + this.setArrowOrigin((sourceX - resX) + Math.floor(sourceWidth / 2)); + break; + + case St.Side.LEFT: + case St.Side.RIGHT: + switch (alignment) { + case St.Align.START: + resY = sourceY; + break; + case St.Align.MIDDLE: + resY = sourceY - Math.floor((natHeight - sourceHeight) / 2); + break; + case St.Align.END: + resY = sourceY - (natHeight - sourceHeight); + break; + } + + resY = Math.min(resY, primary.y + primary.height - natHeight); + resY = Math.max(resY, primary.y); + + this.setArrowOrigin((sourceY - resY) + Math.floor(sourceHeight / 2)); + break; + } + + let parent = this.actor.get_parent(); + let success, x, y; + while (!success) { + [success, x, y] = parent.transform_stage_point(resX, resY); + parent = parent.get_parent(); + } + + // Actually set the position + this.actor.x = Math.floor(x); + this.actor.y = Math.floor(y); + }, + // @origin: Coordinate specifying middle of the arrow, along // the Y axis for St.Side.LEFT, St.Side.RIGHT from the top and X axis from // the left for St.Side.TOP and St.Side.BOTTOM. diff --git a/js/ui/extensionSystem.js b/js/ui/extensionSystem.js index 944e2e546..5315d90c3 100644 --- a/js/ui/extensionSystem.js +++ b/js/ui/extensionSystem.js @@ -51,6 +51,12 @@ function loadExtension(dir, enabled, type) { return; } } + + if (extensions[meta.uuid] != undefined) { + global.logError(baseErrorString + "extension already loaded"); + return; + } + // Encourage people to add this if (!meta['url']) { global.log(baseErrorString + 'Warning: Missing "url" property in metadata.json'); diff --git a/js/ui/iconGrid.js b/js/ui/iconGrid.js index 05abe1bdb..3625316e8 100644 --- a/js/ui/iconGrid.js +++ b/js/ui/iconGrid.js @@ -135,9 +135,12 @@ function IconGrid(params) { IconGrid.prototype = { _init: function(params) { - params = Params.parse(params, { rowLimit: null, columnLimit: null }); + params = Params.parse(params, { rowLimit: null, + columnLimit: null, + xAlign: St.Align.MIDDLE }); this._rowLimit = params.rowLimit; this._colLimit = params.columnLimit; + this._xAlign = params.xAlign; this.actor = new St.BoxLayout({ style_class: 'icon-grid', vertical: true }); @@ -189,9 +192,19 @@ IconGrid.prototype = { let [nColumns, usedWidth] = this._computeLayout(availWidth); - let overallPaddingX = Math.floor((availWidth - usedWidth) / 2); + let leftPadding; + switch(this._xAlign) { + case St.Align.START: + leftPadding = 0; + break; + case St.Align.MIDDLE: + leftPadding = Math.floor((availWidth - usedWidth) / 2); + break; + case St.Align.END: + leftPadding = availWidth - usedWidth; + } - let x = box.x1 + overallPaddingX; + let x = box.x1 + leftPadding; let y = box.y1; let columnIndex = 0; let rowIndex = 0; @@ -231,7 +244,7 @@ IconGrid.prototype = { if (columnIndex == 0) { y += this._item_size + this._spacing; - x = box.x1 + overallPaddingX; + x = box.x1 + leftPadding; } else { x += this._item_size + this._spacing; } diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js index a028d3206..95183c6a1 100644 --- a/js/ui/messageTray.js +++ b/js/ui/messageTray.js @@ -12,6 +12,7 @@ const St = imports.gi.St; const Tweener = imports.ui.tweener; const Main = imports.ui.main; +const BoxPointer = imports.ui.boxpointer; const Params = imports.misc.params; const ANIMATION_TIME = 0.2; @@ -778,15 +779,19 @@ MessageTray.prototype = { this._summaryBin.child = this._summary; this._summaryBin.opacity = 0; - this._summaryNotificationBin = new St.Bin({ name: 'summary-notification-bin', - anchor_gravity: Clutter.Gravity.NORTH_EAST, - reactive: true, - track_hover: true }); - this.actor.add_actor(this._summaryNotificationBin); - this._summaryNotificationBin.lower_bottom(); - this._summaryNotificationBin.hide(); + this._summaryMotionId = 0; + + this._summaryNotificationBoxPointer = new BoxPointer.BoxPointer(St.Side.BOTTOM, + { reactive: true, + track_hover: true }); + this._summaryNotificationBoxPointer.actor.style_class = 'summary-notification-boxpointer'; + this.actor.add_actor(this._summaryNotificationBoxPointer.actor); + this._summaryNotificationBoxPointer.actor.lower_bottom(); + this._summaryNotificationBoxPointer.actor.hide(); + this._summaryNotification = null; this._clickedSummaryItem = null; + this._clickedSummaryItemAllocationChangedId = 0; this._expandedSummaryItem = null; this._summaryItemTitleWidth = 0; @@ -817,7 +822,7 @@ MessageTray.prototype = { Main.chrome.addActor(this.actor, { affectsStruts: false, visibleInOverview: true }); Main.chrome.trackActor(this._notificationBin); - Main.chrome.trackActor(this._summaryNotificationBin); + Main.chrome.trackActor(this._summaryNotificationBoxPointer.actor); global.gdk_screen.connect('monitors-changed', Lang.bind(this, this._setSizePosition)); @@ -859,7 +864,6 @@ MessageTray.prototype = { // These work because of their anchor_gravity this._summaryBin.x = primary.width; - this._summaryNotificationBin.x = primary.width; }, contains: function(source) { @@ -964,7 +968,7 @@ MessageTray.prototype = { needUpdate = true; } if (this._clickedSummaryItem && this._clickedSummaryItem.source == source) { - this._clickedSummaryItem = null; + this._unsetClickedSummaryItem(); needUpdate = true; } @@ -993,7 +997,7 @@ MessageTray.prototype = { if (!this._locked) return; this._locked = false; - this._clickedSummaryItem = null; + this._unsetClickedSummaryItem(); this._updateState(); }, @@ -1116,7 +1120,7 @@ MessageTray.prototype = { if (!this._clickedSummaryItem || this._clickedSummaryItem != summaryItem) this._clickedSummaryItem = summaryItem; else - this._clickedSummaryItem = null; + this._unsetClickedSummaryItem(); this._updateState(); }, @@ -1478,25 +1482,51 @@ MessageTray.prototype = { if (index != -1) this._notificationQueue.splice(index, 1); - this._summaryNotificationBin.child = this._summaryNotification.actor; + this._summaryNotificationBoxPointer.bin.child = this._summaryNotification.actor; this._summaryNotification.grabFocus(true); - this._summaryNotificationBin.opacity = 0; - this._summaryNotificationBin.y = this.actor.height; - this._summaryNotificationBin.show(); - if (!this._summaryNotificationExpandedId) this._summaryNotificationExpandedId = this._summaryNotification.connect('expanded', Lang.bind(this, this._onSummaryNotificationExpanded)); this._summaryNotification.expand(false); + + this._clickedSummaryItemAllocationChangedId = + this._clickedSummaryItem.actor.connect('allocation-changed', + Lang.bind(this, this._adjustNotificationBoxPointerPosition)); + // _clickedSummaryItem.actor can change absolute postiion without changing allocation + this._summaryMotionId = this._summary.connect('allocation-changed', + Lang.bind(this, this._adjustNotificationBoxPointerPosition)); + + this._summaryNotificationBoxPointer.actor.opacity = 0; + this._summaryNotificationBoxPointer.actor.show(); + this._adjustNotificationBoxPointerPosition(); + + this._summaryNotificationState = State.SHOWNING; + this._summaryNotificationBoxPointer.animateAppear(Lang.bind(this, function() { + this._summaryNotificationState = State.SHOWN; + })); + }, + + _adjustNotificationBoxPointerPosition: function() { + // The position of the arrow origin should be the same as center of this._clickedSummaryItem.actor + if (!this._clickedSummaryItem) + return; + + this._summaryNotificationBoxPointer.setPosition(this._clickedSummaryItem.actor, 0, St.Align.MIDDLE); + }, + + _unsetClickedSummaryItem: function() { + if (this._clickedSummaryItemAllocationChangedId) { + this._clickedSummaryItem.actor.disconnect(this._clickedSummaryItemAllocationChangedId); + this._summary.disconnect(this._summaryMotionId); + this._clickedSummaryItemAllocationChangedId = 0; + this._summaryMotionId = 0; + } + + this._clickedSummaryItem = null; }, _onSummaryNotificationExpanded: function() { - this._tween(this._summaryNotificationBin, '_summaryNotificationState', State.SHOWN, - { y: this.actor.height - this._summaryNotificationBin.height, - opacity: 255, - time: ANIMATION_TIME, - transition: 'easeOutQuad' - }); + this._adjustNotificationBoxPointerPosition(); }, _hideSummaryNotification: function() { @@ -1504,25 +1534,18 @@ MessageTray.prototype = { this._summaryNotification.disconnect(this._summaryNotificationExpandedId); this._summaryNotificationExpandedId = 0; } - // Unset this._clickedSummaryItem if we are no longer showing the summary if (this._summaryState != State.SHOWN) - this._clickedSummaryItem = null; - this._summaryNotification.ungrabFocus(); + this._unsetClickedSummaryItem(); - this._tween(this._summaryNotificationBin, '_summaryNotificationState', State.HIDDEN, - { y: this.actor.height, - opacity: 0, - time: ANIMATION_TIME, - transition: 'easeOutQuad', - onComplete: this._hideSummaryNotificationCompleted, - onCompleteScope: this - }); + this._summaryNotification.ungrabFocus(); + this._summaryNotificationState = State.HIDING; + this._summaryNotificationBoxPointer.animateDisappear(Lang.bind(this, this._hideSummaryNotificationCompleted)); }, _hideSummaryNotificationCompleted: function() { - this._summaryNotificationBin.hide(); - this._summaryNotificationBin.child = null; + this._summaryNotificationState = State.HIDDEN; + this._summaryNotificationBoxPointer.bin.child = null; this._summaryNotification.collapseCompleted(); let summaryNotification = this._summaryNotification; this._summaryNotification = null; diff --git a/js/ui/panel.js b/js/ui/panel.js index 4abbcf26c..466d189a0 100644 --- a/js/ui/panel.js +++ b/js/ui/panel.js @@ -32,7 +32,8 @@ const SPINNER_SPEED = 0.02; const STANDARD_TRAY_ICON_ORDER = ['a11y', 'display', 'keyboard', 'volume', 'bluetooth', 'network', 'battery']; const STANDARD_TRAY_ICON_SHELL_IMPLEMENTATION = { - 'a11y': imports.ui.status.accessibility.ATIndicator + 'a11y': imports.ui.status.accessibility.ATIndicator, + 'volume': imports.ui.status.volume.Indicator, }; function AnimatedIcon(name, size) { diff --git a/js/ui/popupMenu.js b/js/ui/popupMenu.js index 7e60fe821..8694a601c 100644 --- a/js/ui/popupMenu.js +++ b/js/ui/popupMenu.js @@ -16,8 +16,6 @@ const Tweener = imports.ui.tweener; const Gettext = imports.gettext.domain('gnome-shell'); const _ = Gettext.gettext; -const POPUP_ANIMATION_TIME = 0.1; - function Switch() { this._init.apply(this, arguments); } @@ -178,7 +176,7 @@ PopupSliderMenuItem.prototype = { if (isNaN(value)) // Avoid spreading NaNs around throw TypeError('The slider value must be a number'); - this._displayValue = this._value = Math.max(Math.min(value, 1), 0); + this._value = Math.max(Math.min(value, 1), 0); this._slider = new St.DrawingArea({ style_class: 'popup-slider-menu-item', reactive: true }); this.actor.set_child(this._slider); @@ -193,7 +191,7 @@ PopupSliderMenuItem.prototype = { if (isNaN(value)) throw TypeError('The slider value must be a number'); - this._displayValue = this._value = Math.max(Math.min(value, 1), 0); + this._value = Math.max(Math.min(value, 1), 0); this._slider.queue_repaint(); }, @@ -233,7 +231,7 @@ PopupSliderMenuItem.prototype = { cr.stroke(); let handleY = height / 2; - let handleX = handleRadius + (width - 2 * handleRadius) * this._displayValue; + let handleX = handleRadius + (width - 2 * handleRadius) * this._value; let color = new Clutter.Color(); themeNode.get_foreground_color(color); @@ -271,8 +269,7 @@ PopupSliderMenuItem.prototype = { Clutter.ungrab_pointer(); this._dragging = false; - this._value = this._displayValue; - this.emit('value-changed', this._value); + this.emit('drag-end'); } return true; }, @@ -301,8 +298,9 @@ PopupSliderMenuItem.prototype = { newvalue = 1; else newvalue = (relX - handleRadius) / (width - 2 * handleRadius); - this._displayValue = newvalue; + this._value = newvalue; this._slider.queue_repaint(); + this.emit('value-changed', this._value); }, get value() { @@ -313,9 +311,10 @@ PopupSliderMenuItem.prototype = { let key = event.get_key_symbol(); if (key == Clutter.Right || key == Clutter.Left) { let delta = key == Clutter.Right ? 0.1 : -0.1; - this._value = this._displayValue = Math.max(0, Math.min(this._value + delta, 1)); + this._value = Math.max(0, Math.min(this._value + delta, 1)); this._slider.queue_repaint(); this.emit('value-changed', this._value); + this.emit('drag-end'); return true; } return false; @@ -552,7 +551,6 @@ PopupMenu.prototype = { let [minWidth, minHeight, natWidth, natHeight] = this.actor.get_preferred_size(); - let menuX, menuY; let menuWidth = natWidth, menuHeight = natHeight; // Position the non-pointing axis @@ -575,75 +573,12 @@ PopupMenu.prototype = { this._boxPointer._arrowSide = this._arrowSide = St.Side.LEFT; } } - switch (this._arrowSide) { - case St.Side.TOP: - menuY = sourceY + sourceHeight + this._gap; - break; - case St.Side.BOTTOM: - menuY = sourceY - menuHeight - this._gap; - break; - case St.Side.LEFT: - menuX = sourceX + sourceWidth + this._gap; - break; - case St.Side.RIGHT: - menuX = sourceX - menuWidth - this._gap; - break; - } - // Now align and position the pointing axis, making sure - // it fits on screen - switch (this._arrowSide) { - case St.Side.TOP: - case St.Side.BOTTOM: - switch (this._alignment) { - case St.Align.START: - menuX = sourceX; - break; - case St.Align.MIDDLE: - menuX = sourceX - Math.floor((menuWidth - sourceWidth) / 2); - break; - case St.Align.END: - menuX = sourceX - (menuWidth - sourceWidth); - break; - } - - menuX = Math.min(menuX, primary.x + primary.width - menuWidth); - menuX = Math.max(menuX, primary.x); - - this._boxPointer.setArrowOrigin((sourceX - menuX) + Math.floor(sourceWidth / 2)); - break; - - case St.Side.LEFT: - case St.Side.RIGHT: - switch (this._alignment) { - case St.Align.START: - menuY = sourceY; - break; - case St.Align.MIDDLE: - menuY = sourceY - Math.floor((menuHeight - sourceHeight) / 2); - break; - case St.Align.END: - menuY = sourceY - (menuHeight - sourceHeight); - break; - } - - menuY = Math.min(menuY, primary.y + primary.height - menuHeight); - menuY = Math.max(menuY, primary.y); - - this._boxPointer.setArrowOrigin((sourceY - menuY) + Math.floor(sourceHeight / 2)); - break; - } - - // Actually set the position - this.actor.x = Math.floor(menuX); - this.actor.y = Math.floor(menuY); + this._boxPointer.setPosition(this.sourceActor, this._gap, this._alignment); // Now show it - this.actor.opacity = 0; this.actor.reactive = true; - Tweener.addTween(this.actor, { opacity: 255, - transition: "easeOutQuad", - time: POPUP_ANIMATION_TIME }); + this._boxPointer.animateAppear(); this.isOpen = true; this.emit('open-state-changed', true); }, @@ -657,11 +592,7 @@ PopupMenu.prototype = { if (this._activeMenuItem) this._activeMenuItem.setActive(false); this.actor.reactive = false; - Tweener.addTween(this.actor, { opacity: 0, - transition: "easeOutQuad", - time: POPUP_ANIMATION_TIME, - onComplete: Lang.bind(this, function () { this.actor.hide(); })}); - + this._boxPointer.animateDisappear(); this.isOpen = false; this.emit('open-state-changed', false); }, diff --git a/js/ui/status/volume.js b/js/ui/status/volume.js new file mode 100644 index 000000000..92f17def1 --- /dev/null +++ b/js/ui/status/volume.js @@ -0,0 +1,206 @@ +/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ + +const DBus = imports.dbus; +const Lang = imports.lang; +const Mainloop = imports.mainloop; +const Shell = imports.gi.Shell; +const Gvc = imports.gi.Gvc; +const Signals = imports.signals; +const St = imports.gi.St; + +const PanelMenu = imports.ui.panelMenu; +const PopupMenu = imports.ui.popupMenu; + +const Gettext = imports.gettext.domain('gnome-shell'); +const _ = Gettext.gettext; + +const VOLUME_MAX = 65536.0; /* PA_VOLUME_NORM */ + +function Indicator() { + this._init.apply(this, arguments); +} + +Indicator.prototype = { + __proto__: PanelMenu.SystemStatusButton.prototype, + + _init: function() { + PanelMenu.SystemStatusButton.prototype._init.call(this, 'audio-volume-muted', null); + + this._control = new Gvc.MixerControl({ name: 'GNOME Shell Volume Control' }); + this._control.connect('ready', Lang.bind(this, this._onControlReady)); + this._control.connect('default-sink-changed', Lang.bind(this, this._readOutput)); + this._control.connect('default-source-changed', Lang.bind(this, this._readInput)); + this._control.connect('stream-added', Lang.bind(this, this._maybeShowInput)); + this._control.connect('stream-removed', Lang.bind(this, this._maybeShowInput)); + + this._output = null; + this._outputVolumeId = 0; + this._outputMutedId = 0; + this._outputSwitch = new PopupMenu.PopupSwitchMenuItem(_("Output: Muted"), false); + this._outputSwitch.connect('toggled', Lang.bind(this, this._switchToggled, '_output')); + this._outputSlider = new PopupMenu.PopupSliderMenuItem(0); + this._outputSlider.connect('value-changed', Lang.bind(this, this._sliderChanged, '_output')); + this._outputSlider.connect('drag-end', Lang.bind(this, this._notifyVolumeChange)); + this.menu.addMenuItem(this._outputSwitch); + this.menu.addMenuItem(this._outputSlider); + + this._separator = new PopupMenu.PopupSeparatorMenuItem(); + this.menu.addMenuItem(this._separator); + + this._input = null; + this._inputVolumeId = 0; + this._inputMutedId = 0; + this._inputSwitch = new PopupMenu.PopupSwitchMenuItem(_("Input: Muted"), false); + this._inputSwitch.connect('toggled', Lang.bind(this, this._switchToggled, '_input')); + this._inputSlider = new PopupMenu.PopupSliderMenuItem(0); + this._inputSlider.connect('value-changed', Lang.bind(this, this._sliderChanged, '_input')); + this._inputSlider.connect('drag-end', Lang.bind(this, this._notifyVolumeChange)); + this.menu.addMenuItem(this._inputSwitch); + this.menu.addMenuItem(this._inputSlider); + + this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); + this.menu.addAction(_("Sound Preferences"), function() { + let p = new Shell.Process({ args: ['gnome-control-center', 'volume'] }); + p.run(); + }); + + this._control.open(); + }, + + _onControlReady: function() { + this._readOutput(); + this._readInput(); + }, + + _readOutput: function() { + if (this._outputVolumeId) { + this._output.disconnect(this._outputVolumeId); + this._output.disconnect(this._outputMutedId); + this._outputVolumeId = 0; + this._outputMutedId = 0; + } + this._output = this._control.get_default_sink(); + if (this._output) { + this._outputMutedId = this._output.connect('notify::is-muted', Lang.bind(this, this._mutedChanged, '_output')); + this._outputVolumeId = this._output.connect('notify::volume', Lang.bind(this, this._volumeChanged, '_output')); + this._mutedChanged (null, null, '_output'); + this._volumeChanged (null, null, '_output'); + this.setIcon(this._volumeToIcon(this._output.volume)); + } else { + this._outputSwitch.label.text = _("Output: Muted"); + this._outputSwitch.setToggleState(false); + this.setIcon('audio-volume-muted-symbolic'); + } + }, + + _readInput: function() { + if (this._inputVolumeId) { + this._input.disconnect(this._inputVolumeId); + this._input.disconnect(this._inputMutedId); + this._inputVolumeId = 0; + this._inputMutedId = 0; + } + this._input = this._control.get_default_source(); + if (this._input) { + this._inputMutedId = this._input.connect('notify::is-muted', Lang.bind(this, this._mutedChanged, '_input')); + this._inputVolumeId = this._input.connect('notify::volume', Lang.bind(this, this._volumeChanged, '_input')); + this._mutedChanged (null, null, '_input'); + this._volumeChanged (null, null, '_input'); + } else { + this._separator.actor.hide(); + this._inputSwitch.actor.hide(); + this._inputSlider.actor.hide(); + } + }, + + _maybeShowInput: function() { + // only show input widgets if any application is recording audio + let showInput = false; + let recordingApps = this._control.get_source_outputs(); + if (this._source && recordingApps) { + for (let i = 0; i < recordingApp.length; i++) { + let outputStream = recordingApp[i]; + let id = outputStream.get_application_id(); + // but skip gnome-volume-control and pavucontrol + // (that appear as recording because they show the input level) + if (!id || (id != 'org.gnome.VolumeControl' && id != 'org.PulseAudio.pavucontrol')) { + showInput = true; + break; + } + } + } + if (showInput) { + this._separator.actor.show(); + this._inputSwitch.actor.show(); + this._inputSlider.actor.show(); + } else { + this._separator.actor.hide(); + this._inputSwitch.actor.hide(); + this._inputSlider.actor.hide(); + } + }, + + _volumeToIcon: function(volume) { + if (volume <= 0) { + return 'audio-volume-muted'; + } else { + let v = volume / VOLUME_MAX; + if (v < 0.33) + return 'audio-volume-low'; + if (v > 0.8) + return 'audio-volume-high'; + return 'audio-volume-medium'; + } + }, + + _sliderChanged: function(slider, value, property) { + if (this[property] == null) { + log ('Volume slider changed for %s, but %s does not exist'.format(property, property)); + return; + } + this[property].volume = value * VOLUME_MAX; + this[property].push_volume(); + }, + + _notifyVolumeChange: function() { + global.play_theme_sound('audio-volume-change'); + }, + + _switchToggled: function(switchItem, state, property) { + if (this[property] == null) { + log ('Volume mute switch toggled for %s, but %s does not exist'.format(property, property)); + return; + } + this[property].change_is_muted(!state); + this._notifyVolumeChange(); + }, + + _mutedChanged: function(object, param_spec, property) { + let muted = this[property].is_muted; + let toggleSwitch = this[property+'Switch']; + toggleSwitch.setToggleState(!muted); + this._updateLabel(property); + if (property == '_output') { + if (muted) + this.setIcon('audio-volume-muted'); + else + this.setIcon(this._volumeToIcon(this._output.volume)); + } + }, + + _volumeChanged: function(object, param_spec, property) { + this[property+'Slider'].setValue(this[property].volume / VOLUME_MAX); + this._updateLabel(property); + if (property == '_output') + this.setIcon(this._volumeToIcon(this._output.volume)); + }, + + _updateLabel: function(property) { + let label; + if (this[property].is_muted) + label = (property == '_output' ? _("Output: Muted") : _("Input: Muted")); + else + label = (property == '_output' ? _("Output: %3.0f%%") : _("Input: %3.0f%%")).format(this[property].volume / VOLUME_MAX * 100); + this[property+'Switch'].label.text = label; + } +}; diff --git a/js/ui/statusMenu.js b/js/ui/statusMenu.js index 8de3b6511..9aae26cb4 100644 --- a/js/ui/statusMenu.js +++ b/js/ui/statusMenu.js @@ -31,6 +31,8 @@ StatusMenuButton.prototype = { this.actor.set_child(box); this._gdm = Gdm.UserManager.ref_default(); + this._gdm.queue_load() + this._user = this._gdm.get_user(GLib.get_user_name()); this._presence = new GnomeSession.Presence(); @@ -48,27 +50,31 @@ StatusMenuButton.prototype = { this._presence.connect('StatusChanged', Lang.bind(this, this._updatePresenceIcon)); this._presence.getStatus(Lang.bind(this, this._updatePresenceIcon)); - this._name = new St.Label({ text: this._user.get_real_name() }); + this._name = new St.Label(); box.add(this._name, { y_align: St.Align.MIDDLE, y_fill: false }); - this._userNameChangedId = this._user.connect('notify::display-name', Lang.bind(this, this._updateUserName)); + this._userLoadedId = this._user.connect('notify::is-loaded', Lang.bind(this, this._updateUserName)); + this._userChangedId = this._user.connect('changed', Lang.bind(this, this._updateUserName)); this._createSubMenu(); - this._gdm.connect('users-loaded', Lang.bind(this, this._updateSwitchUser)); + this._gdm.connect('notify::is-loaded', Lang.bind(this, this._updateSwitchUser)); this._gdm.connect('user-added', Lang.bind(this, this._updateSwitchUser)); this._gdm.connect('user-removed', Lang.bind(this, this._updateSwitchUser)); }, _onDestroy: function() { - this._user.disconnect(this._userNameChangedId); + this._user.disconnect(this._userLoadedId); + this._user.disconnect(this._userChangedId); }, _updateUserName: function() { - this._name.set_text(this._user.get_real_name()); + if (this._user.is_loaded) + this._name.set_text(this._user.get_real_name()); + else + this._name.set_text(""); }, _updateSwitchUser: function() { - let users = this._gdm.list_users(); - if (users.length > 1) + if (this._gdm.can_switch ()) this._loginScreenItem.actor.show(); else this._loginScreenItem.actor.hide(); diff --git a/po/POTFILES.in b/po/POTFILES.in index 97b956b71..2aa7d7c48 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -14,6 +14,7 @@ js/ui/placeDisplay.js js/ui/popupMenu.js js/ui/runDialog.js js/ui/statusMenu.js +js/ui/status/accessibility.js js/ui/windowAttentionHandler.js js/ui/workspacesView.js src/gdmuser/gdm-user.c diff --git a/po/es.po b/po/es.po index 455f3a583..30a2537c1 100644 --- a/po/es.po +++ b/po/es.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: gnome-shell.master\n" "Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=gnome-" "shell&component=general\n" -"POT-Creation-Date: 2010-10-04 19:52+0000\n" -"PO-Revision-Date: 2010-10-13 14:29+0200\n" +"POT-Creation-Date: 2010-10-18 08:59+0000\n" +"PO-Revision-Date: 2010-10-18 11:12+0200\n" "Last-Translator: Jorge González \n" "Language-Team: Español \n" "MIME-Version: 1.0\n" @@ -284,7 +284,6 @@ msgstr "" "la imagen del ratón." #: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:7 -#| msgid "Enabled" msgid "Enable lens mode" msgstr "Activar el modo lente" @@ -504,58 +503,58 @@ msgid "Undo" msgstr "Deshacer" #. TODO - _quit() doesn't really work on apps in state STARTING yet -#: ../js/ui/panel.js:461 +#: ../js/ui/panel.js:468 #, c-format msgid "Quit %s" msgstr "Salir de %s" -#: ../js/ui/panel.js:486 +#: ../js/ui/panel.js:493 msgid "Preferences" msgstr "Preferencias" #. Translators: This is the time format with date used #. in 24-hour mode. -#: ../js/ui/panel.js:572 +#: ../js/ui/panel.js:579 msgid "%a %b %e, %R:%S" msgstr "%a %e de %b, %R:%S" -#: ../js/ui/panel.js:573 +#: ../js/ui/panel.js:580 msgid "%a %b %e, %R" msgstr "%a %e de %b, %R" #. Translators: This is the time format without date used #. in 24-hour mode. -#: ../js/ui/panel.js:577 +#: ../js/ui/panel.js:584 msgid "%a %R:%S" msgstr "%a %R:%S" -#: ../js/ui/panel.js:578 +#: ../js/ui/panel.js:585 msgid "%a %R" msgstr "%a %R" #. Translators: This is a time format with date used #. for AM/PM. -#: ../js/ui/panel.js:585 +#: ../js/ui/panel.js:592 msgid "%a %b %e, %l:%M:%S %p" msgstr "%a %e de %b, %H:%M:%S" -#: ../js/ui/panel.js:586 +#: ../js/ui/panel.js:593 msgid "%a %b %e, %l:%M %p" msgstr "%a %e de %b, %H:%M" #. Translators: This is a time format without date used #. for AM/PM. -#: ../js/ui/panel.js:590 +#: ../js/ui/panel.js:597 msgid "%a %l:%M:%S %p" msgstr "%a %H:%M:%S" -#: ../js/ui/panel.js:591 +#: ../js/ui/panel.js:598 msgid "%a %l:%M %p" msgstr "%a %H:%M" #. Button on the left side of the panel. #. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview". -#: ../js/ui/panel.js:736 +#: ../js/ui/panel.js:743 msgid "Activities" msgstr "Actividades" @@ -590,43 +589,86 @@ msgstr "Introduzca un comando:" msgid "Execution of '%s' failed:" msgstr "Falló la ejecución de «%s»:" -#: ../js/ui/statusMenu.js:91 +#: ../js/ui/statusMenu.js:97 msgid "Available" msgstr "Disponible" -#: ../js/ui/statusMenu.js:95 +#: ../js/ui/statusMenu.js:101 msgid "Busy" msgstr "Ocupado" -#: ../js/ui/statusMenu.js:99 +#: ../js/ui/statusMenu.js:105 msgid "Invisible" msgstr "Invisible" -#: ../js/ui/statusMenu.js:106 +#: ../js/ui/statusMenu.js:112 msgid "Account Information..." msgstr "Información de la cuenta…" -#: ../js/ui/statusMenu.js:110 -#| msgid "System Preferences..." +#: ../js/ui/statusMenu.js:116 msgid "System Settings..." msgstr "Ajustes del sistema…" -#: ../js/ui/statusMenu.js:117 +#: ../js/ui/statusMenu.js:123 msgid "Lock Screen" msgstr "Bloquear la pantalla" -#: ../js/ui/statusMenu.js:121 +#: ../js/ui/statusMenu.js:127 msgid "Switch User" msgstr "Cambiar de usuario" -#: ../js/ui/statusMenu.js:126 +#: ../js/ui/statusMenu.js:132 msgid "Log Out..." msgstr "Salir…" -#: ../js/ui/statusMenu.js:130 +#: ../js/ui/statusMenu.js:136 msgid "Shut Down..." msgstr "Apagar…" +#: ../js/ui/status/accessibility.js:88 +msgid "Screen Reader" +msgstr "Lector de pantalla" + +#: ../js/ui/status/accessibility.js:91 +msgid "Screen Keyboard" +msgstr "Teclado en pantalla" + +#: ../js/ui/status/accessibility.js:94 +msgid "Visual Alerts" +msgstr "Alertas visuales" + +#: ../js/ui/status/accessibility.js:97 +msgid "Sticky Keys" +msgstr "Teclas persistentes" + +#: ../js/ui/status/accessibility.js:100 +msgid "Slow Keys" +msgstr "Teclas lentas" + +#: ../js/ui/status/accessibility.js:103 +msgid "Bounce Keys" +msgstr "Rechazo de teclas" + +#: ../js/ui/status/accessibility.js:106 +msgid "Mouse Keys" +msgstr "Teclas del ratón" + +#: ../js/ui/status/accessibility.js:110 +msgid "Universal Access Settings" +msgstr "Preferencias del acceso universal" + +#: ../js/ui/status/accessibility.js:163 +msgid "High Contrast" +msgstr "Contraste alto" + +#: ../js/ui/status/accessibility.js:202 +msgid "Large Text" +msgstr "Texto:" + +#: ../js/ui/status/accessibility.js:223 +msgid "Zoom" +msgstr "Ampliación" + #: ../js/ui/windowAttentionHandler.js:43 #, c-format msgid "%s has finished starting" diff --git a/po/et.po b/po/et.po index dea27b92a..b2ef72cfa 100644 --- a/po/et.po +++ b/po/et.po @@ -1,30 +1,30 @@ -# GNOME Kesta eesti keele tõlge. -# Estonian translation of GNOME Shell. -# -# Copyright (C) 2010 The GNOME Project. +# Estonian translation for gnome-shell. +# Copyright (C) 2010 The Gnome Project # This file is distributed under the same license as the gnome-shell package. -# -# Ivar Smolin , 2010. +# Mattias Põldaru , 2010. # msgid "" msgstr "" "Project-Id-Version: gnome-shell master\n" "Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=gnome-" "shell&component=general\n" -"POT-Creation-Date: 2010-09-10 02:04+0000\n" -"PO-Revision-Date: 2010-09-10 08:29+0300\n" -"Last-Translator: Ivar Smolin \n" -"Language-Team: Estonian \n" +"POT-Creation-Date: 2010-10-16 19:24+0000\n" +"PO-Revision-Date: 2010-10-17 17:16+0300\n" +"Last-Translator: Mattias Põldaru \n" +"Language-Team: Estonian \n" +"Language: et\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Poedit-Language: Estonian\n" +"X-Poedit-Country: Estonia\n" msgid "GNOME Shell" -msgstr "GNOME Kest" +msgstr "GNOME kest" msgid "Window management and application launching" -msgstr "Aknahaldus ja rakenduste käivitamine" +msgstr "Aknahaldur ja rakenduste käivitaja" msgid "Clock" msgstr "Kell" @@ -36,18 +36,21 @@ msgid "" "Allows access to internal debugging and monitoring tools using the Alt-F2 " "dialog." msgstr "" +"Lubab ligipääsu sisemistele silumise ja monitoorimise tööriistadele Alt-F2 " +"dialoogi kaudu." msgid "Custom format of the clock" -msgstr "Kella kohandatud vorming" +msgstr "Kellaaaja kohandatud vorming" msgid "Enable internal tools useful for developers and testers from Alt-F2" msgstr "" +"Arendajate ja testijate jaoks sisemiste tööriistade lubamine Alt-F2 alt" msgid "File extension used for storing the screencast" -msgstr "" +msgstr "Faililaiend, mida kasutatakse ekraanivideo salvestamisel" msgid "Framerate used for recording screencasts." -msgstr "" +msgstr "Ekraanivideo lindistamisel kasutatav kaadrikiirus." msgid "" "GNOME Shell extensions have a uuid property; this key lists extensions which " @@ -55,7 +58,7 @@ msgid "" msgstr "" msgid "History for command (Alt-F2) dialog" -msgstr "" +msgstr "Käsudialoogi (Alt-F2) ajalugu" msgid "Hour format" msgstr "Tundide vorming" @@ -64,17 +67,21 @@ msgid "" "If true and format is either \"12-hour\" or \"24-hour\", display date in the " "clock, in addition to time." msgstr "" +"Kui tõene ja vorming on kas \"12-tundi\" või \"24-tundi\", kuvatakse " +"kellaaja kõrval ka kuupäeva." msgid "" "If true and format is either \"12-hour\" or \"24-hour\", display seconds in " "time." msgstr "" +"Kui tõene ja vorming on kas \"12-tundi\" või \"24-tundi\", kuvatakse " +"kellaaega koos sekunditega." msgid "If true, display the ISO week date in the calendar." -msgstr "" +msgstr "Kui tõene, kuvatakse kalendris kuupäeva ISO nädalate järgi." msgid "List of desktop file IDs for favorite applications" -msgstr "" +msgstr "Lemmikrakenduste töölauafailide ID-de loend" msgid "Overview workspace view mode" msgstr "" @@ -91,13 +98,13 @@ msgid "" msgstr "" msgid "Show date in clock" -msgstr "Kuupäeva näitamine koos kellaajaga" +msgstr "Kell näitab kuupäeva" msgid "Show the week date in the calendar" -msgstr "" +msgstr "Kalendris näidatakse nädala kuupäeva" msgid "Show time with seconds" -msgstr "Aja näitamine koos sekunditega" +msgstr "Kellaaega näidatakse sekunditega" msgid "" "The applications corresponding to these identifiers will be displayed in the " @@ -147,80 +154,190 @@ msgid "" msgstr "" msgid "Uuids of extensions to disable" -msgstr "" +msgstr "Keelatavate laienduste UUID-d" msgid "Whether to collect stats about applications usage" msgstr "" +msgid "Clip the crosshairs at the center" +msgstr "Niitristi keskel on auk" + +msgid "Color of the crosshairs" +msgstr "Niitristi värvus" + +msgid "" +"Determines the length of the vertical and horizontal lines that make up the " +"crosshairs." +msgstr "" + +msgid "" +"Determines the position of the magnified mouse image within the magnified " +"view and how it reacts to system mouse movement. The values are - none: no " +"mouse tracking; - centered: the mouse image is displayed at the center of " +"the zoom region (which also represents the point under the system mouse) and " +"the magnified contents are scrolled as the system mouse moves; - " +"proportional: the position of the magnified mouse in the zoom region is " +"proportionally the same as the position of the system mouse on screen; - " +"push: when the magnified mouse intersects a boundary of the zoom region, the " +"contents are scrolled into view." +msgstr "" + +msgid "" +"Determines the transparency of the crosshairs, from fully opaque to fully " +"transparent." +msgstr "" +"Määrab niitristi läbipaistvuse, alates täiesti läbipaistmatust kuni täiesti " +"läbipaistvani." + +msgid "" +"Determines whether the crosshairs intersect the magnified mouse sprite, or " +"are clipped such that the ends of the horizontal and vertical lines surround " +"the mouse image." +msgstr "" +"Määrab, kas niitrist kattub suurendatud hiire pildiga või on keskelt ära " +"lõigatud nii, et jooned ümbritsevad hiirekursori pilti." + +msgid "Enable lens mode" +msgstr "Läätsede režiimi lubamine" + +msgid "" +"Enables/disables display of crosshairs centered on the magnified mouse " +"sprite." +msgstr "Lubab/keelab niitristi kuvamise suurendatud hiirekursori kohal." + +msgid "" +"For centered mouse tracking, when the system pointer is at or near the edge " +"of the screen, the magnified contents continue to scroll such that the " +"screen edge moves into the magnified view." +msgstr "" + +msgid "Length of the crosshairs" +msgstr "Niitristi pikkus" + +msgid "Magnification factor" +msgstr "Suurendustegur" + +msgid "Mouse Tracking Mode" +msgstr "Hiire jälitamise režiim" + +msgid "Opacity of the crosshairs" +msgstr "Niitristi läbipaistvus" + +msgid "Screen position" +msgstr "Ekraani asukoht" + +msgid "Scroll magnified contents beyond the edges of the desktop" +msgstr "Suurendusklaas võib liikuda töölauapiiridest väljapoole" + +msgid "Show or hide crosshairs" +msgstr "Niitristi kuvamine või peitmine" + +msgid "Show or hide the magnifier" +msgstr "Suurendusklaasi kuvamine või peitmine" + +msgid "Show or hide the magnifier and all of its zoom regions." +msgstr "" +"Suurendusklaasi ja selle kõigi suurenduspiirkondade kuvamine või peitmine." + +msgid "" +"The color of the the vertical and horizontal lines that make up the " +"crosshairs." +msgstr "Niitristi püst- ja rõhtjoone värvus." + +msgid "" +"The magnified view either fills the entire screen, or occupies the top-half, " +"bottom-half, left-half, or right-half of the screen." +msgstr "" +"Suurendatud vaade täidab kas kogu ekraani või täidab ülemise, alumise, " +"vasaku või parema ekraanipoole." + +msgid "" +"The power of the magnification. A value of 1.0 means no magnification. A " +"value of 2.0 doubles the size." +msgstr "Suurendustegur. 1,0 tähendab originaalsuurust. 2,0 muudab kaks korda." + +msgid "Thickness of the crosshairs" +msgstr "Niitristi paksus" + +msgid "" +"Whether the magnified view should be centered over the location of the " +"system mouse and move with it." +msgstr "" +"Kas suurendatud vaate keskkoht peaks asetsema süsteemi hiire kohal ning " +"liikuma sellega kaasa." + +msgid "Width of the vertical and horizontal lines that make up the crosshairs." +msgstr "Niitristi moodustavate püst- ja rõhtjoone laius" + msgid "Clock Format" -msgstr "Kella vorming" +msgstr "Kella formaat" msgid "Clock Preferences" msgstr "Kella eelistused" msgid "Panel Display" -msgstr "" +msgstr "Paneelikuva" msgid "Show seco_nds" -msgstr "_Sekundid on nähtaval" +msgstr "_Sekundeid näidatakse" msgid "Show the _date" -msgstr "_Kuupäev on nähtaval" +msgstr "_Kuupäeva näidatakse" msgid "_12 hour format" -msgstr "_12-tunnine vorming" +msgstr "_12 tunni vorming" msgid "_24 hour format" -msgstr "_24-tunnine vorming" +msgstr "_24 tunni vorming" #. **** Applications **** msgid "APPLICATIONS" -msgstr "RAKENDUSED" +msgstr "Rakendused" msgid "PREFERENCES" -msgstr "EELISTUSED" +msgstr "Eelistused" msgid "New Window" msgstr "Uus aken" msgid "Remove from Favorites" -msgstr "Eemalda lemmikute hulgast" +msgstr "Eemalda lemmikutest" msgid "Add to Favorites" -msgstr "Lisa lemmikute hulka" +msgstr "Lisa lemmikutesse" msgid "Drag here to add favorites" -msgstr "" +msgstr "Lemmikute lisamiseks lohista need siia" #, c-format msgid "%s has been added to your favorites." -msgstr "" +msgstr "%s lisati lemmikutesse." #, c-format msgid "%s has been removed from your favorites." -msgstr "" +msgstr "%s eemaldati lemmikutest." msgid "Find" -msgstr "" +msgstr "Otsi" msgid "Searching..." -msgstr "" +msgstr "Otsimine..." msgid "No matching results." -msgstr "Sobivaid vasteid ei leitud." +msgstr "Tulemused puuduvad." #. **** Places **** #. Translators: This is in the sense of locations for documents, #. network locations, etc. msgid "PLACES & DEVICES" -msgstr "ASUKOHAD JA SEADMED" +msgstr "Asukohad ja seadmed" #. **** Documents **** msgid "RECENT ITEMS" -msgstr "VIIMASED KIRJED" +msgstr "Hiljutised dokumendid" msgid "No extensions installed" -msgstr "Laiendusi ei ole paigaldatud" +msgstr "Ühtegi laiendust pole paigaldatud" msgid "Enabled" msgstr "Lubatud" @@ -232,16 +349,16 @@ msgid "Error" msgstr "Viga" msgid "Out of date" -msgstr "" +msgstr "Pole värske" msgid "View Source" -msgstr "" +msgstr "Kuva lähtekoodi" msgid "Web Page" msgstr "Veebileht" msgid "Undo" -msgstr "Unusta" +msgstr "Võta tagasi" #. TODO - _quit() doesn't really work on apps in state STARTING yet #, c-format @@ -254,66 +371,64 @@ msgstr "Eelistused" #. Translators: This is the time format with date used #. in 24-hour mode. msgid "%a %b %e, %R:%S" -msgstr "%a, %e. %b %R:%S" +msgstr "%a, %e. %b, %R:%S" msgid "%a %b %e, %R" -msgstr "%a, %e. %b %R" +msgstr "%a, %e. %b, %R" #. Translators: This is the time format without date used #. in 24-hour mode. msgid "%a %R:%S" -msgstr "%a %R:%S" +msgstr "%A %R:%S" msgid "%a %R" -msgstr "%a %R" +msgstr "%A %R" #. Translators: This is a time format with date used #. for AM/PM. msgid "%a %b %e, %l:%M:%S %p" -msgstr "%a, %e. %b %l:%M:%S %p" +msgstr "%a, %e. %b, %l:%M:%S %p" msgid "%a %b %e, %l:%M %p" -msgstr "%a, %e. %b %l:%MS %p" +msgstr "%a, %e. %b, %l:%M %p" #. Translators: This is a time format without date used #. for AM/PM. msgid "%a %l:%M:%S %p" -msgstr "%a %l:%M:%S %p" +msgstr "%A, %l:%M:%S %p" msgid "%a %l:%M %p" -msgstr "%a %l:%M %p" +msgstr "%A, %l:%M %p" #. Button on the left side of the panel. #. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview". msgid "Activities" -msgstr "Ülevaade" +msgstr "Tegevused" #, c-format msgid "Failed to unmount '%s'" -msgstr "Tõrge '%s' lahtihaakimisel" +msgstr "'%s' lahtihaakimine nurjus" msgid "Retry" msgstr "Proovi uuesti" msgid "Connect to..." -msgstr "" +msgstr "Ühendumine..." -#. Translators: the "ON" and "OFF" strings are used in the -#. toggle switches in the status area menus, and must be SHORT. -#. If you don't have suitable short words, consider initials, -#. "0"/"1", "⚪"/"⚫", etc. -msgid "ON" -msgstr "" - -msgid "OFF" -msgstr "" +#. Translators: this MUST be either "toggle-switch-us" +#. (for toggle switches containing the English words +#. "ON" and "OFF") or "toggle-switch-intl" (for toggle +#. switches containing "◯" and "|"). Other values will +#. simply result in invisible toggle switches. +msgid "toggle-switch-us" +msgstr "toggle-switch-intl" msgid "Please enter a command:" msgstr "Palun sisesta käsk:" #, c-format msgid "Execution of '%s' failed:" -msgstr "" +msgstr "'%s' käivitamine nurjus:" msgid "Available" msgstr "Saadaval" @@ -327,8 +442,8 @@ msgstr "Nähtamatu" msgid "Account Information..." msgstr "Konto andmed..." -msgid "System Preferences..." -msgstr "Süsteemi eelistused..." +msgid "System Settings..." +msgstr "Süsteemi sätted..." msgid "Lock Screen" msgstr "Lukusta ekraan" @@ -340,11 +455,11 @@ msgid "Log Out..." msgstr "Logi välja..." msgid "Shut Down..." -msgstr "Seiska..." +msgstr "Lülita välja..." #, c-format msgid "%s has finished starting" -msgstr "%s lõpetas käivitumise" +msgstr "%s läks käima" #, c-format msgid "'%s' is ready" @@ -352,13 +467,13 @@ msgstr "'%s' on valmis" msgid "" "Can't add a new workspace because maximum workspaces limit has been reached." -msgstr "" +msgstr "Pole võimalik uut tööala lisada, kuna tööalade piir on saavutatud." msgid "Can't remove the first workspace." -msgstr "" +msgstr "Esimest tööala pole võimalik eemaldada." msgid "Less than a minute ago" -msgstr "Vähem kui minut aega tagasi" +msgstr "Vähem kui minuti eest" #, c-format msgid "%d minute ago" @@ -369,14 +484,14 @@ msgstr[1] "%d minutit tagasi" #, c-format msgid "%d hour ago" msgid_plural "%d hours ago" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%d tund tagasi" +msgstr[1] "%d tundi tagasi" #, c-format msgid "%d day ago" msgid_plural "%d days ago" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%d päev tagasi" +msgstr[1] "%d päeva tagasi" #, c-format msgid "%d week ago" @@ -385,7 +500,7 @@ msgstr[0] "%d nädal tagasi" msgstr[1] "%d nädalat tagasi" msgid "Home Folder" -msgstr "Kodukataloog" +msgstr "Kodukaust" #. Translators: this is the same string as the one found in #. * nautilus diff --git a/po/it.po b/po/it.po index c31d4d375..f20c6a6a6 100644 --- a/po/it.po +++ b/po/it.po @@ -3,13 +3,14 @@ # This file is distributed under the same license as the gnome-shell package. # # Milo Casagrande , 2009, 2010. +# Luca Ferretti , 2010. msgid "" msgstr "" "Project-Id-Version: gnome-shell\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2010-09-02 21:30+0200\n" -"PO-Revision-Date: 2010-08-27 21:12+0200\n" -"Last-Translator: Milo Casagrande \n" +"POT-Creation-Date: 2010-10-18 10:54+0200\n" +"PO-Revision-Date: 2010-10-18 11:04+0200\n" +"Last-Translator: Luca Ferretti \n" "Language-Team: Italian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -225,6 +226,132 @@ msgid "Whether to collect stats about applications usage" msgstr "" "Indica se raccogliere statistiche riguardo l'utilizzo delle applicazioni" +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:1 +msgid "Clip the crosshairs at the center" +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:2 +msgid "Color of the crosshairs" +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:3 +msgid "" +"Determines the length of the vertical and horizontal lines that make up the " +"crosshairs." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:4 +msgid "" +"Determines the position of the magnified mouse image within the magnified " +"view and how it reacts to system mouse movement. The values are - none: no " +"mouse tracking; - centered: the mouse image is displayed at the center of " +"the zoom region (which also represents the point under the system mouse) and " +"the magnified contents are scrolled as the system mouse moves; - " +"proportional: the position of the magnified mouse in the zoom region is " +"proportionally the same as the position of the system mouse on screen; - " +"push: when the magnified mouse intersects a boundary of the zoom region, the " +"contents are scrolled into view." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:5 +msgid "" +"Determines the transparency of the crosshairs, from fully opaque to fully " +"transparent." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:6 +msgid "" +"Determines whether the crosshairs intersect the magnified mouse sprite, or " +"are clipped such that the ends of the horizontal and vertical lines surround " +"the mouse image." +msgstr "" + +# (ndt) o abilitata? +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:7 +msgid "Enable lens mode" +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:8 +msgid "" +"Enables/disables display of crosshairs centered on the magnified mouse " +"sprite." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:9 +msgid "" +"For centered mouse tracking, when the system pointer is at or near the edge " +"of the screen, the magnified contents continue to scroll such that the " +"screen edge moves into the magnified view." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:10 +msgid "Length of the crosshairs" +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:11 +msgid "Magnification factor" +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:12 +msgid "Mouse Tracking Mode" +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:13 +msgid "Opacity of the crosshairs" +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:14 +msgid "Screen position" +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:15 +msgid "Scroll magnified contents beyond the edges of the desktop" +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:16 +msgid "Show or hide crosshairs" +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:17 +msgid "Show or hide the magnifier" +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:18 +msgid "Show or hide the magnifier and all of its zoom regions." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:19 +msgid "" +"The color of the the vertical and horizontal lines that make up the " +"crosshairs." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:20 +msgid "" +"The magnified view either fills the entire screen, or occupies the top-half, " +"bottom-half, left-half, or right-half of the screen." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:21 +msgid "" +"The power of the magnification. A value of 1.0 means no magnification. A " +"value of 2.0 doubles the size." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:22 +msgid "Thickness of the crosshairs" +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:23 +msgid "" +"Whether the magnified view should be centered over the location of the " +"system mouse and move with it." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:24 +msgid "Width of the vertical and horizontal lines that make up the crosshairs." +msgstr "" + #: ../data/clock-preferences.ui.h:1 msgid "Clock Format" msgstr "Formato ora" @@ -254,27 +381,27 @@ msgid "_24 hour format" msgstr "Formato _24 ore" #. **** Applications **** -#: ../js/ui/appDisplay.js:384 ../js/ui/dash.js:778 +#: ../js/ui/appDisplay.js:316 ../js/ui/dash.js:778 msgid "APPLICATIONS" msgstr "Applicazioni" -#: ../js/ui/appDisplay.js:416 +#: ../js/ui/appDisplay.js:348 msgid "PREFERENCES" msgstr "Preferenze" -#: ../js/ui/appDisplay.js:721 +#: ../js/ui/appDisplay.js:648 msgid "New Window" msgstr "Nuova finestra" -#: ../js/ui/appDisplay.js:725 +#: ../js/ui/appDisplay.js:652 msgid "Remove from Favorites" msgstr "Rimuovi dai preferiti" -#: ../js/ui/appDisplay.js:726 +#: ../js/ui/appDisplay.js:653 msgid "Add to Favorites" msgstr "Aggiungi ai preferiti" -#: ../js/ui/appDisplay.js:1033 +#: ../js/ui/appDisplay.js:830 msgid "Drag here to add favorites" msgstr "Trascinare qui per aggiungere ai preferiti" @@ -349,59 +476,59 @@ msgid "Undo" msgstr "Annulla" #. TODO - _quit() doesn't really work on apps in state STARTING yet -#: ../js/ui/panel.js:473 +#: ../js/ui/panel.js:468 #, c-format msgid "Quit %s" msgstr "Chiudi %s" -#: ../js/ui/panel.js:498 +#: ../js/ui/panel.js:493 msgid "Preferences" msgstr "Preferenze" #. Translators: This is the time format with date used #. in 24-hour mode. -#: ../js/ui/panel.js:584 +#: ../js/ui/panel.js:579 msgid "%a %b %e, %R:%S" msgstr "%a %e %b, %k.%M.%S" # (ndt) proviamo col k, se non funge, sappiamo il perché... -#: ../js/ui/panel.js:585 +#: ../js/ui/panel.js:580 msgid "%a %b %e, %R" msgstr "%a %e %b, %k.%M" #. Translators: This is the time format without date used #. in 24-hour mode. -#: ../js/ui/panel.js:589 +#: ../js/ui/panel.js:584 msgid "%a %R:%S" msgstr "%a %k.%M.%S" -#: ../js/ui/panel.js:590 +#: ../js/ui/panel.js:585 msgid "%a %R" msgstr "%a %k.%M" #. Translators: This is a time format with date used #. for AM/PM. -#: ../js/ui/panel.js:597 +#: ../js/ui/panel.js:592 msgid "%a %b %e, %l:%M:%S %p" msgstr "%a %e %b, %l.%M.%S %P" -#: ../js/ui/panel.js:598 +#: ../js/ui/panel.js:593 msgid "%a %b %e, %l:%M %p" msgstr "%a %e %b, %l.%M %P" #. Translators: This is a time format without date used #. for AM/PM. -#: ../js/ui/panel.js:602 +#: ../js/ui/panel.js:597 msgid "%a %l:%M:%S %p" msgstr "%a %l.%M.%S %P" -#: ../js/ui/panel.js:603 +#: ../js/ui/panel.js:598 msgid "%a %l:%M %p" msgstr "%a %l.%M %P" #. Button on the left side of the panel. #. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview". -#: ../js/ui/panel.js:748 +#: ../js/ui/panel.js:743 msgid "Activities" msgstr "Attività" @@ -419,17 +546,14 @@ msgstr "Riprova" msgid "Connect to..." msgstr "Connetti a..." -#. Translators: the "ON" and "OFF" strings are used in the -#. toggle switches in the status area menus, and must be SHORT. -#. If you don't have suitable short words, consider initials, -#. "0"/"1", "⚪"/"⚫", etc. -#: ../js/ui/popupMenu.js:30 ../js/ui/popupMenu.js:40 -msgid "ON" -msgstr "On" - -#: ../js/ui/popupMenu.js:31 ../js/ui/popupMenu.js:45 -msgid "OFF" -msgstr "Off" +#. Translators: this MUST be either "toggle-switch-us" +#. (for toggle switches containing the English words +#. "ON" and "OFF") or "toggle-switch-intl" (for toggle +#. switches containing "◯" and "|"). Other values will +#. simply result in invisible toggle switches. +#: ../js/ui/popupMenu.js:33 +msgid "toggle-switch-us" +msgstr "toggle-switch-intl" #: ../js/ui/runDialog.js:233 msgid "Please enter a command:" @@ -440,42 +564,86 @@ msgstr "Inserire un comando:" msgid "Execution of '%s' failed:" msgstr "Esecuzione di «%s» non riuscita:" -#: ../js/ui/statusMenu.js:91 +#: ../js/ui/statusMenu.js:97 msgid "Available" msgstr "Disponibile" -#: ../js/ui/statusMenu.js:95 +#: ../js/ui/statusMenu.js:101 msgid "Busy" msgstr "Non disponibile" -#: ../js/ui/statusMenu.js:99 +#: ../js/ui/statusMenu.js:105 msgid "Invisible" msgstr "Invisibile" -#: ../js/ui/statusMenu.js:106 +#: ../js/ui/statusMenu.js:112 msgid "Account Information..." msgstr "Informazioni account..." -#: ../js/ui/statusMenu.js:110 -msgid "System Preferences..." -msgstr "Preferenze di sistema..." +#: ../js/ui/statusMenu.js:116 +msgid "System Settings..." +msgstr "Impostazioni di sistema..." -#: ../js/ui/statusMenu.js:117 +#: ../js/ui/statusMenu.js:123 msgid "Lock Screen" msgstr "Blocca schermo" -#: ../js/ui/statusMenu.js:121 +#: ../js/ui/statusMenu.js:127 msgid "Switch User" msgstr "Cambia utente" -#: ../js/ui/statusMenu.js:126 +#: ../js/ui/statusMenu.js:132 msgid "Log Out..." msgstr "Termina sessione..." -#: ../js/ui/statusMenu.js:130 +#: ../js/ui/statusMenu.js:136 msgid "Shut Down..." msgstr "Arresta..." +#: ../js/ui/status/accessibility.js:88 +msgid "Screen Reader" +msgstr "Lettore schermo" + +#: ../js/ui/status/accessibility.js:91 +msgid "Screen Keyboard" +msgstr "Tastiera a schermo" + +#: ../js/ui/status/accessibility.js:94 +msgid "Visual Alerts" +msgstr "Allerte visive" + +#: ../js/ui/status/accessibility.js:97 +msgid "Sticky Keys" +msgstr "Permanenza tasti" + +#: ../js/ui/status/accessibility.js:100 +msgid "Slow Keys" +msgstr "Rallentamento tasti" + +#: ../js/ui/status/accessibility.js:103 +msgid "Bounce Keys" +msgstr "Pressione ravvicinata tasti" + +#: ../js/ui/status/accessibility.js:106 +msgid "Mouse Keys" +msgstr "Mouse da tastiera" + +#: ../js/ui/status/accessibility.js:110 +msgid "Universal Access Settings" +msgstr "Impostazioni accesso universale" + +#: ../js/ui/status/accessibility.js:163 +msgid "High Contrast" +msgstr "Contrasto elevato" + +#: ../js/ui/status/accessibility.js:202 +msgid "Large Text" +msgstr "Caratteri grandi" + +#: ../js/ui/status/accessibility.js:223 +msgid "Zoom" +msgstr "Ingrandimento" + #: ../js/ui/windowAttentionHandler.js:43 #, c-format msgid "%s has finished starting" @@ -499,32 +667,32 @@ msgstr "" msgid "Can't remove the first workspace." msgstr "Impossibile rimuovere il primo spazio di lavoro." -#: ../src/shell-global.c:1105 +#: ../src/shell-global.c:1196 msgid "Less than a minute ago" msgstr "Meno di un minuto fa" -#: ../src/shell-global.c:1109 +#: ../src/shell-global.c:1200 #, c-format msgid "%d minute ago" msgid_plural "%d minutes ago" msgstr[0] "%d minuto fa" msgstr[1] "%d minuti fa" -#: ../src/shell-global.c:1114 +#: ../src/shell-global.c:1205 #, c-format msgid "%d hour ago" msgid_plural "%d hours ago" msgstr[0] "%d ora fa" msgstr[1] "%d ore fa" -#: ../src/shell-global.c:1119 +#: ../src/shell-global.c:1210 #, c-format msgid "%d day ago" msgid_plural "%d days ago" msgstr[0] "%d giorno fa" msgstr[1] "%d giorni fa" -#: ../src/shell-global.c:1124 +#: ../src/shell-global.c:1215 #, c-format msgid "%d week ago" msgid_plural "%d weeks ago" @@ -555,3 +723,9 @@ msgstr "Cerca" #, c-format msgid "%1$s: %2$s" msgstr "%1$s: %2$s" + +#~ msgid "ON" +#~ msgstr "On" + +#~ msgid "OFF" +#~ msgstr "Off" diff --git a/po/nb.po b/po/nb.po index 7149abba0..f82b0bf31 100644 --- a/po/nb.po +++ b/po/nb.po @@ -1,15 +1,15 @@ # Norwegian bokmål translation of gnome-shell. # Copyright (C) 2009 THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-shell package. -# Kjartan Maraas , 2009-2010. +# Kjartan Maraas , 2009-2010. # msgid "" msgstr "" "Project-Id-Version: gnome-shell 2.31.x\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2010-10-06 16:05+0200\n" -"PO-Revision-Date: 2010-10-06 16:07+0200\n" -"Last-Translator: Kjartan Maraas \n" +"POT-Creation-Date: 2010-10-16 21:23+0200\n" +"PO-Revision-Date: 2010-10-16 21:24+0200\n" +"Last-Translator: Kjartan Maraas \n" "Language-Team: Norwegian bokmål \n" "Language: \n" "MIME-Version: 1.0\n" @@ -37,9 +37,7 @@ msgstr "Tilpass klokken på panelet" msgid "" "Allows access to internal debugging and monitoring tools using the Alt-F2 " "dialog." -msgstr "" -"Tillat tilgang til interne feilsøkings og overvåkingsverktøy ved å bruke Alt-" -"F2-dialogen." +msgstr "Tillat tilgang til interne feilsøkings og overvåkingsverktøy ved å bruke Alt-F2-dialogen." #: ../data/org.gnome.shell.gschema.xml.in.h:2 msgid "Custom format of the clock" @@ -427,58 +425,58 @@ msgid "Undo" msgstr "Angre" #. TODO - _quit() doesn't really work on apps in state STARTING yet -#: ../js/ui/panel.js:461 +#: ../js/ui/panel.js:468 #, c-format msgid "Quit %s" msgstr "Avslutt %s" -#: ../js/ui/panel.js:486 +#: ../js/ui/panel.js:493 msgid "Preferences" msgstr "Brukervalg" #. Translators: This is the time format with date used #. in 24-hour mode. -#: ../js/ui/panel.js:572 +#: ../js/ui/panel.js:579 msgid "%a %b %e, %R:%S" msgstr "%a %e %b, %R.%S" -#: ../js/ui/panel.js:573 +#: ../js/ui/panel.js:580 msgid "%a %b %e, %R" msgstr "%a %e %b, %R" #. Translators: This is the time format without date used #. in 24-hour mode. -#: ../js/ui/panel.js:577 +#: ../js/ui/panel.js:584 msgid "%a %R:%S" msgstr "%a %R.%S" -#: ../js/ui/panel.js:578 +#: ../js/ui/panel.js:585 msgid "%a %R" msgstr "%a %R" #. Translators: This is a time format with date used #. for AM/PM. -#: ../js/ui/panel.js:585 +#: ../js/ui/panel.js:592 msgid "%a %b %e, %l:%M:%S %p" msgstr "%a %e %b, %l.%M.%S %p" -#: ../js/ui/panel.js:586 +#: ../js/ui/panel.js:593 msgid "%a %b %e, %l:%M %p" msgstr "%a %e %b, %l.%M %p" #. Translators: This is a time format without date used #. for AM/PM. -#: ../js/ui/panel.js:590 +#: ../js/ui/panel.js:597 msgid "%a %l:%M:%S %p" msgstr "%a %l.%M.%S %p" -#: ../js/ui/panel.js:591 +#: ../js/ui/panel.js:598 msgid "%a %l:%M %p" msgstr "%a %l.%M %p" #. Button on the left side of the panel. #. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview". -#: ../js/ui/panel.js:736 +#: ../js/ui/panel.js:743 msgid "Activities" msgstr "Aktiviteter" @@ -513,39 +511,39 @@ msgstr "Oppgi en kommando:" msgid "Execution of '%s' failed:" msgstr "Kjøring av «%s» feilet:" -#: ../js/ui/statusMenu.js:91 +#: ../js/ui/statusMenu.js:97 msgid "Available" msgstr "Tilgjengelig" -#: ../js/ui/statusMenu.js:95 +#: ../js/ui/statusMenu.js:101 msgid "Busy" msgstr "Opptatt" -#: ../js/ui/statusMenu.js:99 +#: ../js/ui/statusMenu.js:105 msgid "Invisible" msgstr "Usynlig" -#: ../js/ui/statusMenu.js:106 +#: ../js/ui/statusMenu.js:112 msgid "Account Information..." msgstr "Kontoinformasjon..." -#: ../js/ui/statusMenu.js:110 +#: ../js/ui/statusMenu.js:116 msgid "System Settings..." msgstr "Innstillinger for systemet..." -#: ../js/ui/statusMenu.js:117 +#: ../js/ui/statusMenu.js:123 msgid "Lock Screen" msgstr "Lås skjerm" -#: ../js/ui/statusMenu.js:121 +#: ../js/ui/statusMenu.js:127 msgid "Switch User" msgstr "Bytt bruker" -#: ../js/ui/statusMenu.js:126 +#: ../js/ui/statusMenu.js:132 msgid "Log Out..." msgstr "Logg ut..." -#: ../js/ui/statusMenu.js:130 +#: ../js/ui/statusMenu.js:136 msgid "Shut Down..." msgstr "Avslutt..." diff --git a/po/nl.po b/po/nl.po index bc631564c..3d6a0dc33 100644 --- a/po/nl.po +++ b/po/nl.po @@ -3,14 +3,16 @@ # This file is distributed under the same license as the gnome-shell package. # # Sander Dijkhuis , 2009, 2010. +# Reinout van Schouwen , 2010 msgid "" msgstr "" "Project-Id-Version: gnome-shell master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2010-03-31 13:34+0200\n" -"PO-Revision-Date: 2010-04-02 22:18+0200\n" -"Last-Translator: Sander Dijkhuis \n" +"POT-Creation-Date: 2010-10-19 23:19+0200\n" +"PO-Revision-Date: 2010-10-19 14:12+0100\n" +"Last-Translator: Reinout van Schouwen \n" "Language-Team: Dutch \n" +"Language: nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -24,32 +26,369 @@ msgstr "Gnome Shell" msgid "Window management and application launching" msgstr "Vensterbeheer en toepassingen starten" +#: ../data/gnome-shell-clock-preferences.desktop.in.in.h:1 +msgid "Clock" +msgstr "Klok" + +#: ../data/gnome-shell-clock-preferences.desktop.in.in.h:2 +msgid "Customize the panel clock" +msgstr "De paneelklok aanpassen" + +#: ../data/org.gnome.shell.gschema.xml.in.h:1 +msgid "" +"Allows access to internal debugging and monitoring tools using the Alt-F2 " +"dialog." +msgstr "" +"Geeft toegang tot interne debugging- en observatieprogramma's met behulp van " +"het Alt-F2-dialoogvenster." + +#: ../data/org.gnome.shell.gschema.xml.in.h:2 +msgid "Custom format of the clock" +msgstr "Aangepaste indeling van de klok" + +#: ../data/org.gnome.shell.gschema.xml.in.h:3 +msgid "Enable internal tools useful for developers and testers from Alt-F2" +msgstr "" +"Interne hulpprogramma's inschakelen die nuttig zijn voor gebruikers en " +"testers van Alt-F2" + +#: ../data/org.gnome.shell.gschema.xml.in.h:4 +msgid "File extension used for storing the screencast" +msgstr "Bestandsextensie voor het opslaan van de screencast" + +#: ../data/org.gnome.shell.gschema.xml.in.h:5 +msgid "Framerate used for recording screencasts." +msgstr "Framerate voor het opnemen van de screencasts." + +#: ../data/org.gnome.shell.gschema.xml.in.h:6 +msgid "" +"GNOME Shell extensions have a uuid property; this key lists extensions which " +"should not be loaded." +msgstr "" +"Gnome Shell-uitbreidingen hebben een uuid-eigenschap; deze sleutel toont " +"uitbreidingen die niet geladen mogen worden." + +#: ../data/org.gnome.shell.gschema.xml.in.h:7 +msgid "History for command (Alt-F2) dialog" +msgstr "Geschiedenis voor het opdracht-dialoogvenster (Alt-F2)" + +#: ../data/org.gnome.shell.gschema.xml.in.h:8 +msgid "Hour format" +msgstr "Uurindeling" + +#: ../data/org.gnome.shell.gschema.xml.in.h:9 +msgid "" +"If true and format is either \"12-hour\" or \"24-hour\", display date in the " +"clock, in addition to time." +msgstr "" +"Indien ingeschakeld en de indeling is ofwel ‘12-uur’ of ‘24-uur’, behalve de " +"tijd ook de datum in de klok weergeven." + +#: ../data/org.gnome.shell.gschema.xml.in.h:10 +msgid "" +"If true and format is either \"12-hour\" or \"24-hour\", display seconds in " +"time." +msgstr "" +"Indien ingeschakeld en de indeling is ofwel ‘12-uur’ of ‘24-uur’, seconden " +"in de klok weergeven." + +#: ../data/org.gnome.shell.gschema.xml.in.h:11 +msgid "If true, display the ISO week date in the calendar." +msgstr "Indien ingeschakeld worden weeknummers in de kalender getoond." + +#: ../data/org.gnome.shell.gschema.xml.in.h:12 +msgid "List of desktop file IDs for favorite applications" +msgstr "Lijst van desktopbestand-id's voor favoriete toepassingen" + +#: ../data/org.gnome.shell.gschema.xml.in.h:13 +msgid "Overview workspace view mode" +msgstr "Werkbladoverzichtsmodus" + +#: ../data/org.gnome.shell.gschema.xml.in.h:14 +msgid "" +"Sets the GStreamer pipeline used to encode recordings. It follows the syntax " +"used for gst-launch. The pipeline should have an unconnected sink pad where " +"the recorded video is recorded. It will normally have a unconnected source " +"pad; output from that pad will be written into the output file. However the " +"pipeline can also take care of its own output - this might be used to send " +"the output to an icecast server via shout2send or similar. When unset or set " +"to an empty value, the default pipeline will be used. This is currently " +"'videorate ! theoraenc ! oggmux' and records to Ogg Theora." +msgstr "" + +#: ../data/org.gnome.shell.gschema.xml.in.h:15 +msgid "Show date in clock" +msgstr "Datum tonen in klok" + +#: ../data/org.gnome.shell.gschema.xml.in.h:16 +msgid "Show the week date in the calendar" +msgstr "Weeknummers tonen in kalender" + +#: ../data/org.gnome.shell.gschema.xml.in.h:17 +msgid "Show time with seconds" +msgstr "Tijd tonen inclusief seconden" + +#: ../data/org.gnome.shell.gschema.xml.in.h:18 +msgid "" +"The applications corresponding to these identifiers will be displayed in the " +"favorites area." +msgstr "" + +#: ../data/org.gnome.shell.gschema.xml.in.h:19 +msgid "" +"The filename for recorded screencasts will be a unique filename based on the " +"current date, and use this extension. It should be changed when recording to " +"a different container format." +msgstr "" + +#: ../data/org.gnome.shell.gschema.xml.in.h:20 +msgid "" +"The framerate of the resulting screencast recordered by GNOME Shell's " +"screencast recorder in frames-per-second." +msgstr "" + +#: ../data/org.gnome.shell.gschema.xml.in.h:21 +msgid "The gstreamer pipeline used to encode the screencast" +msgstr "De gstreamer-pijplijn voor het coderen van de screencast" + +#: ../data/org.gnome.shell.gschema.xml.in.h:22 +msgid "" +"The selected workspace view mode in the overview. Supported values are " +"\"single\" and \"grid\"." +msgstr "" +"De geselecteerde werkbladmodus in het overzicht. Ondersteunde waarden zijn " +"‘single’ en ‘grid’." + +#: ../data/org.gnome.shell.gschema.xml.in.h:23 +msgid "" +"The shell normally monitors active applications in order to present the most " +"used ones (e.g. in launchers). While this data will be kept private, you may " +"want to disable this for privacy reasons. Please note that doing so won't " +"remove already saved data." +msgstr "" + +#: ../data/org.gnome.shell.gschema.xml.in.h:24 +msgid "" +"This key specifies the format used by the panel clock when the format key is " +"set to \"custom\". You can use conversion specifiers understood by strftime" +"() to obtain a specific format. See the strftime() manual for more " +"information." +msgstr "" +"Deze sleutel geeft aan welke weergave de paneelklok gebruikt wanneer de " +"weergavesleutel is ingsteld op ‘custom’. U kunt opmaakcodes voor de functie " +"‘strftime()’ gebruiken om een bepaalde weergave te verkrijgen. Zie de " +"handleiding van strftime() voor meer informatie." + +#: ../data/org.gnome.shell.gschema.xml.in.h:25 +msgid "" +"This key specifies the hour format used by the panel clock. Possible values " +"are \"12-hour\", \"24-hour\", \"unix\" and \"custom\". If set to \"unix\", " +"the clock will display time in seconds since Epoch, i.e. 1970-01-01. If set " +"to \"custom\", the clock will display time according to the format specified " +"in the custom_format key. Note that if set to either \"unix\" or \"custom\", " +"the show_date and show_seconds keys are ignored." +msgstr "" +"Deze sleutel geeft de uurindeling aan van de paneelklok. Mogelijke waarden " +"zijn ‘12-hour’, ‘24-hour’, ‘unix’ en ’custom’. Als ingesteld op ‘unix’ geeft " +"de klok tijd weer in seconden sinds Epoch, oftewel 01-01-1970. Als ingesteld " +"op ‘custom’ dan geeft de klok tijd weer aan de hand van de indeling die is " +"gespecificeerd in de sleutel custom_format. Merk op dat de sleutels " +"show_date en show_seconds worden genegeerd als de waarde staat op ‘unix’ of " +"‘custom’." + +#: ../data/org.gnome.shell.gschema.xml.in.h:26 +msgid "Uuids of extensions to disable" +msgstr "Uuid's van uit te schakelen uitbreidingen" + +#: ../data/org.gnome.shell.gschema.xml.in.h:27 +msgid "Whether to collect stats about applications usage" +msgstr "Of statistieken worden bijgehouden over toepassingsgebruik" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:1 +msgid "Clip the crosshairs at the center" +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:2 +#, fuzzy +msgid "Color of the crosshairs" +msgstr "Intersecties (kruisjes)" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:3 +msgid "" +"Determines the length of the vertical and horizontal lines that make up the " +"crosshairs." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:4 +msgid "" +"Determines the position of the magnified mouse image within the magnified " +"view and how it reacts to system mouse movement. The values are - none: no " +"mouse tracking; - centered: the mouse image is displayed at the center of " +"the zoom region (which also represents the point under the system mouse) and " +"the magnified contents are scrolled as the system mouse moves; - " +"proportional: the position of the magnified mouse in the zoom region is " +"proportionally the same as the position of the system mouse on screen; - " +"push: when the magnified mouse intersects a boundary of the zoom region, the " +"contents are scrolled into view." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:5 +msgid "" +"Determines the transparency of the crosshairs, from fully opaque to fully " +"transparent." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:6 +msgid "" +"Determines whether the crosshairs intersect the magnified mouse sprite, or " +"are clipped such that the ends of the horizontal and vertical lines surround " +"the mouse image." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:7 +msgid "Enable lens mode" +msgstr "Lensmodus inschakelen" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:8 +msgid "" +"Enables/disables display of crosshairs centered on the magnified mouse " +"sprite." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:9 +msgid "" +"For centered mouse tracking, when the system pointer is at or near the edge " +"of the screen, the magnified contents continue to scroll such that the " +"screen edge moves into the magnified view." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:10 +#, fuzzy +msgid "Length of the crosshairs" +msgstr "Intersecties (kruisjes)" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:11 +msgid "Magnification factor" +msgstr "Vergrotingsfactor" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:12 +msgid "Mouse Tracking Mode" +msgstr "Muisvolgmodus" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:13 +#, fuzzy +msgid "Opacity of the crosshairs" +msgstr "Intersecties (kruisjes)" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:14 +msgid "Screen position" +msgstr "Schermpositie" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:15 +msgid "Scroll magnified contents beyond the edges of the desktop" +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:16 +#, fuzzy +msgid "Show or hide crosshairs" +msgstr "De statusbalk tonen of verbergen" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:17 +#, fuzzy +msgid "Show or hide the magnifier" +msgstr "De statusbalk tonen of verbergen" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:18 +msgid "Show or hide the magnifier and all of its zoom regions." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:19 +msgid "" +"The color of the the vertical and horizontal lines that make up the " +"crosshairs." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:20 +msgid "" +"The magnified view either fills the entire screen, or occupies the top-half, " +"bottom-half, left-half, or right-half of the screen." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:21 +msgid "" +"The power of the magnification. A value of 1.0 means no magnification. A " +"value of 2.0 doubles the size." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:22 +#, fuzzy +msgid "Thickness of the crosshairs" +msgstr "Intersecties (kruisjes)" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:23 +msgid "" +"Whether the magnified view should be centered over the location of the " +"system mouse and move with it." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:24 +msgid "Width of the vertical and horizontal lines that make up the crosshairs." +msgstr "" + +#: ../data/clock-preferences.ui.h:1 +msgid "Clock Format" +msgstr "Klokweergave" + +#: ../data/clock-preferences.ui.h:2 +msgid "Clock Preferences" +msgstr "Klok-voorkeuren" + +#: ../data/clock-preferences.ui.h:3 +msgid "Panel Display" +msgstr "Paneelweergave" + +#: ../data/clock-preferences.ui.h:4 +msgid "Show seco_nds" +msgstr "_Seconden tonen" + +#: ../data/clock-preferences.ui.h:5 +msgid "Show the _date" +msgstr "_Datum tonen" + +#: ../data/clock-preferences.ui.h:6 +msgid "_12 hour format" +msgstr "_12-uursklok" + +#: ../data/clock-preferences.ui.h:7 +msgid "_24 hour format" +msgstr "_24-uursklok" + #. **** Applications **** -#: ../js/ui/appDisplay.js:312 ../js/ui/dash.js:855 +#: ../js/ui/appDisplay.js:316 ../js/ui/dash.js:778 msgid "APPLICATIONS" msgstr "TOEPASSINGEN" -#: ../js/ui/appDisplay.js:344 +#: ../js/ui/appDisplay.js:348 msgid "PREFERENCES" msgstr "VOORKEUREN" -#: ../js/ui/appDisplay.js:734 +#: ../js/ui/appDisplay.js:648 msgid "New Window" msgstr "Nieuw venster" -#: ../js/ui/appDisplay.js:738 +#: ../js/ui/appDisplay.js:652 msgid "Remove from Favorites" msgstr "Uit favorieten verwijderen" -#: ../js/ui/appDisplay.js:739 +#: ../js/ui/appDisplay.js:653 msgid "Add to Favorites" msgstr "Aan favorieten toevoegen" -#: ../js/ui/appDisplay.js:1091 +#: ../js/ui/appDisplay.js:830 msgid "Drag here to add favorites" msgstr "Hierheen slepen om favorieten toe te voegen" -#: ../js/ui/appFavorites.js:89 +#: ../js/ui/appFavorites.js:88 #, c-format msgid "%s has been added to your favorites." msgstr "%s is toegevoegd aan uw favorieten." @@ -59,189 +398,275 @@ msgstr "%s is toegevoegd aan uw favorieten." msgid "%s has been removed from your favorites." msgstr "%s is verwijderd uit uw favorieten." -#: ../js/ui/dash.js:194 +#: ../js/ui/dash.js:142 msgid "Find" msgstr "Zoeken" -#: ../js/ui/dash.js:510 +#: ../js/ui/dash.js:473 msgid "Searching..." msgstr "Zoeken…" -#: ../js/ui/dash.js:524 +#: ../js/ui/dash.js:487 msgid "No matching results." msgstr "Geen overeenkomende resultaten." #. **** Places **** #. Translators: This is in the sense of locations for documents, #. network locations, etc. -#: ../js/ui/dash.js:874 ../js/ui/placeDisplay.js:543 +#: ../js/ui/dash.js:797 ../js/ui/placeDisplay.js:554 msgid "PLACES & DEVICES" msgstr "LOCATIES & APPARATEN" #. **** Documents **** -#: ../js/ui/dash.js:881 ../js/ui/docDisplay.js:489 +#: ../js/ui/dash.js:804 ../js/ui/docDisplay.js:494 msgid "RECENT ITEMS" msgstr "RECENTE ITEMS" -#: ../js/ui/lookingGlass.js:363 +#: ../js/ui/lookingGlass.js:552 msgid "No extensions installed" msgstr "Geen uitbreidingen geïnstalleerd" -#: ../js/ui/lookingGlass.js:400 +#: ../js/ui/lookingGlass.js:589 msgid "Enabled" msgstr "Ingeschakeld" -#: ../js/ui/lookingGlass.js:402 +#: ../js/ui/lookingGlass.js:591 msgid "Disabled" msgstr "Uitgeschakeld" -#: ../js/ui/lookingGlass.js:404 +#: ../js/ui/lookingGlass.js:593 msgid "Error" msgstr "Fout" -#: ../js/ui/lookingGlass.js:406 +#: ../js/ui/lookingGlass.js:595 msgid "Out of date" msgstr "Gedateerd" -#: ../js/ui/lookingGlass.js:431 +#: ../js/ui/lookingGlass.js:620 msgid "View Source" msgstr "Broncode weergeven" -#: ../js/ui/lookingGlass.js:437 +#: ../js/ui/lookingGlass.js:626 msgid "Web Page" msgstr "Webpagina" -#: ../js/ui/overview.js:182 +#: ../js/ui/overview.js:160 msgid "Undo" msgstr "Ongedaan maken" -#. Button on the left side of the panel. -#. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview". -#: ../js/ui/panel.js:388 -msgid "Activities" -msgstr "Activiteiten" +#. TODO - _quit() doesn't really work on apps in state STARTING yet +#: ../js/ui/panel.js:468 +#, c-format +msgid "Quit %s" +msgstr "%s afsluiten" -#. Translators: This is the time format used in 24-hour mode. -#: ../js/ui/panel.js:619 +#: ../js/ui/panel.js:493 +msgid "Preferences" +msgstr "Voorkeuren" + +#. Translators: This is the time format with date used +#. in 24-hour mode. +#: ../js/ui/panel.js:579 +msgid "%a %b %e, %R:%S" +msgstr "%a %e %b, %R:%S" + +#: ../js/ui/panel.js:580 +msgid "%a %b %e, %R" +msgstr "%a %e %b, %R" + +#. Translators: This is the time format without date used +#. in 24-hour mode. +#: ../js/ui/panel.js:584 +msgid "%a %R:%S" +msgstr "%a %R:%S" + +#: ../js/ui/panel.js:585 msgid "%a %R" msgstr "%a %R" -#. Translators: This is a time format used for AM/PM. -#: ../js/ui/panel.js:622 -msgid "%a %l:%M %p" -msgstr "%a %k:%M %p" +#. Translators: This is a time format with date used +#. for AM/PM. +#: ../js/ui/panel.js:592 +msgid "%a %b %e, %l:%M:%S %p" +msgstr "%a %e %b, %l:%M:%S %p" -#: ../js/ui/placeDisplay.js:108 +#: ../js/ui/panel.js:593 +msgid "%a %b %e, %l:%M %p" +msgstr "%a %e %b, %l:%M %p" + +#. Translators: This is a time format without date used +#. for AM/PM. +#: ../js/ui/panel.js:597 +msgid "%a %l:%M:%S %p" +msgstr "%a %l:%M:%S %p" + +#: ../js/ui/panel.js:598 +msgid "%a %l:%M %p" +msgstr "%a %l:%M %p" + +#. Button on the left side of the panel. +#. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview". +#: ../js/ui/panel.js:743 +msgid "Activities" +msgstr "Activiteiten" + +#: ../js/ui/placeDisplay.js:111 #, c-format msgid "Failed to unmount '%s'" msgstr "Ontkoppelen van ‘%s’ mislukt" -#: ../js/ui/placeDisplay.js:111 +#: ../js/ui/placeDisplay.js:114 msgid "Retry" msgstr "Opnieuw" -#: ../js/ui/placeDisplay.js:156 +#: ../js/ui/placeDisplay.js:159 msgid "Connect to..." msgstr "Verbinding maken met…" -#: ../js/ui/runDialog.js:232 +#. Translators: this MUST be either "toggle-switch-us" +#. (for toggle switches containing the English words +#. "ON" and "OFF") or "toggle-switch-intl" (for toggle +#. switches containing "◯" and "|"). Other values will +#. simply result in invisible toggle switches. +#: ../js/ui/popupMenu.js:31 +msgid "toggle-switch-us" +msgstr "toggle-switch-intl" + +#: ../js/ui/runDialog.js:233 msgid "Please enter a command:" msgstr "Voer een opdracht in:" -#: ../js/ui/runDialog.js:376 +#: ../js/ui/runDialog.js:378 #, c-format msgid "Execution of '%s' failed:" msgstr "Uitvoeren van ‘%s’ mislukt:" -#: ../js/ui/statusMenu.js:107 +#: ../js/ui/statusMenu.js:97 msgid "Available" msgstr "Beschikbaar" -#: ../js/ui/statusMenu.js:112 +#: ../js/ui/statusMenu.js:101 msgid "Busy" msgstr "Bezig" -#: ../js/ui/statusMenu.js:117 +#: ../js/ui/statusMenu.js:105 msgid "Invisible" msgstr "Onzichtbaar" -#: ../js/ui/statusMenu.js:126 +#: ../js/ui/statusMenu.js:112 msgid "Account Information..." msgstr "Accountinformatie…" -#: ../js/ui/statusMenu.js:132 -msgid "Sidebar" -msgstr "Zijbalk" +#: ../js/ui/statusMenu.js:116 +msgid "System Settings..." +msgstr "Systeeminstellingen…" -#: ../js/ui/statusMenu.js:142 -msgid "System Preferences..." -msgstr "Systeemvoorkeuren…" - -#: ../js/ui/statusMenu.js:151 +#: ../js/ui/statusMenu.js:123 msgid "Lock Screen" msgstr "Scherm vergrendelen" -#: ../js/ui/statusMenu.js:156 +#: ../js/ui/statusMenu.js:127 msgid "Switch User" msgstr "Gebruiker wisselen" -#: ../js/ui/statusMenu.js:162 +#: ../js/ui/statusMenu.js:132 msgid "Log Out..." msgstr "Afmelden…" -#: ../js/ui/statusMenu.js:167 +#: ../js/ui/statusMenu.js:136 msgid "Shut Down..." msgstr "Afsluiten…" -#. Translators: This is a time format. -#: ../js/ui/widget.js:163 -msgid "%H:%M" -msgstr "%H:%M" +#: ../js/ui/status/accessibility.js:88 +msgid "Screen Reader" +msgstr "Schermlezer" -#: ../js/ui/widget.js:317 -msgid "Applications" -msgstr "Toepassingen" +#: ../js/ui/status/accessibility.js:91 +msgid "Screen Keyboard" +msgstr "Schermtoetsenbord" -#: ../js/ui/widget.js:339 -msgid "Recent Documents" -msgstr "Recente documenten" +#: ../js/ui/status/accessibility.js:94 +msgid "Visual Alerts" +msgstr "Visuele attenderingen" -#: ../js/ui/windowAttentionHandler.js:47 +#: ../js/ui/status/accessibility.js:97 +msgid "Sticky Keys" +msgstr "PlakToetsen" + +#: ../js/ui/status/accessibility.js:100 +msgid "Slow Keys" +msgstr "Trage Toetsen" + +#: ../js/ui/status/accessibility.js:103 +msgid "Bounce Keys" +msgstr "Springende toetsen" + +#: ../js/ui/status/accessibility.js:106 +msgid "Mouse Keys" +msgstr "MuisToetsen" + +#: ../js/ui/status/accessibility.js:110 +msgid "Universal Access Settings" +msgstr "Toegankelijkheidsinstellingen" + +#: ../js/ui/status/accessibility.js:163 +msgid "High Contrast" +msgstr "Hoog contrast" + +#: ../js/ui/status/accessibility.js:202 +msgid "Large Text" +msgstr "Grote tekst" + +#: ../js/ui/status/accessibility.js:223 +msgid "Zoom" +msgstr "Zoomen" + +#: ../js/ui/windowAttentionHandler.js:43 #, c-format msgid "%s has finished starting" msgstr "%s is opgestart" -#: ../js/ui/windowAttentionHandler.js:49 +#: ../js/ui/windowAttentionHandler.js:45 #, c-format msgid "'%s' is ready" msgstr "‘%s’ is klaar" -#: ../src/shell-global.c:967 +#: ../js/ui/workspacesView.js:230 +msgid "" +"Can't add a new workspace because maximum workspaces limit has been reached." +msgstr "" +"Kan geen nieuw werkblad toevoegen omdat maximum aantal werkbladen is bereikt." + +#: ../js/ui/workspacesView.js:247 +msgid "Can't remove the first workspace." +msgstr "Kan het eerste werkblad niet verwijderen" + +#: ../src/shell-global.c:1196 msgid "Less than a minute ago" msgstr "Minder dan een minuut geleden" -#: ../src/shell-global.c:971 +#: ../src/shell-global.c:1200 #, c-format msgid "%d minute ago" msgid_plural "%d minutes ago" msgstr[0] "%d minuut geleden" msgstr[1] "%d minuten geleden" -#: ../src/shell-global.c:976 +#: ../src/shell-global.c:1205 #, c-format msgid "%d hour ago" msgid_plural "%d hours ago" msgstr[0] "%d uur geleden" msgstr[1] "%d uur geleden" -#: ../src/shell-global.c:981 +#: ../src/shell-global.c:1210 #, c-format msgid "%d day ago" msgid_plural "%d days ago" msgstr[0] "%d dag geleden" msgstr[1] "%d dagen geleden" -#: ../src/shell-global.c:986 +#: ../src/shell-global.c:1215 #, c-format msgid "%d week ago" msgid_plural "%d weeks ago" @@ -271,3 +696,15 @@ msgstr "Zoeken" #, c-format msgid "%1$s: %2$s" msgstr "%1$s: %2$s" + +#~ msgid "Sidebar" +#~ msgstr "Zijbalk" + +#~ msgid "%H:%M" +#~ msgstr "%H:%M" + +#~ msgid "Applications" +#~ msgstr "Toepassingen" + +#~ msgid "Recent Documents" +#~ msgstr "Recente documenten" diff --git a/src/Makefile-gdmuser.am b/src/Makefile-gdmuser.am index caba0d89a..84563b891 100644 --- a/src/Makefile-gdmuser.am +++ b/src/Makefile-gdmuser.am @@ -5,6 +5,7 @@ gdmuser_cflags = \ -DDATADIR=\""$(datadir)"\" \ -DG_DISABLE_DEPRECATED \ -DG_LOG_DOMAIN=\"GdmUser\" \ + -DGDM_CACHE_DIR=\""$(localstatedir)/cache/gdm"\" \ $(GDMUSER_CFLAGS) \ $(NULL) diff --git a/src/Makefile-gvc.am b/src/Makefile-gvc.am new file mode 100644 index 000000000..20dd4d835 --- /dev/null +++ b/src/Makefile-gvc.am @@ -0,0 +1,69 @@ +noinst_LTLIBRARIES += libgvc.la + +libgvc_la_CPPFLAGS = \ + $(WARN_CFLAGS) \ + $(GVC_CFLAGS) \ + -I$(srcdir)/gvc/ \ + -DWITH_INTROSPECTION \ + -DG_LOG_DOMAIN="\"Gvc\"" + +libgvc_la_LIBADD = \ + $(GVC_LIBS) + +libgvc_la_LDFLAGS = -avoid-version + +libgvc_la_gir_sources = \ + gvc/gvc-mixer-stream.h \ + gvc/gvc-mixer-stream.c \ + gvc/gvc-channel-map.h \ + gvc/gvc-channel-map.c \ + gvc/gvc-mixer-card.c \ + gvc/gvc-mixer-card.h \ + gvc/gvc-mixer-sink.h \ + gvc/gvc-mixer-sink.c \ + gvc/gvc-mixer-source.h \ + gvc/gvc-mixer-source.c \ + gvc/gvc-mixer-sink-input.h \ + gvc/gvc-mixer-sink-input.c \ + gvc/gvc-mixer-source-output.h \ + gvc/gvc-mixer-source-output.c \ + gvc/gvc-mixer-event-role.h \ + gvc/gvc-mixer-event-role.c \ + gvc/gvc-mixer-control.h \ + gvc/gvc-mixer-control.c \ + gvc/gvc-pulseaudio-fake.h + +libgvc_la_private_sources = \ + gvc/gvc-mixer-stream-private.h \ + gvc/gvc-channel-map-private.h \ + gvc/gvc-mixer-card-private.h \ + gvc/gvc-mixer-control-private.h \ + gvc/gvc-pulseaudio-fake.h + +libgvc_la_SOURCES = \ + $(libgvc_la_gir_sources) \ + $(libgvc_la_private_sources) + +Gvc-1.0.gir: $(G_IR_SCANNER) libgvc.la Makefile + $(AM_V_GEN) $(G_IR_SCANNER) \ + --namespace=Gvc \ + --nsversion=1.0 \ + --warn-all \ + --quiet \ + --libtool="$(LIBTOOL)" \ + --add-include-path=$(builddir) \ + --include=GObject-2.0 \ + --library=libgvc.la \ + --identifier-prefix=Gvc \ + --symbol-prefix=gvc_ \ + $(addprefix $(srcdir)/,$(libgvc_la_gir_sources)) \ + $(INCLUDES) \ + -I $(srcdir) \ + -I $(srcdir)/gvc \ + -DWITH_INTROSPECTION \ + -o $@ +CLEANFILES += Gvc-1.0.gir + +Gvc-1.0.typelib: Gvc-1.0.gir + $(AM_V_GEN) $(G_IR_COMPILER) $< -o $@ +CLEANFILES += Gvc-1.0.typelib diff --git a/src/Makefile.am b/src/Makefile.am index e64082437..6bdc0d261 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -37,6 +37,7 @@ EXTRA_DIST += gnome-shell-clock-preferences.in include Makefile-gdmuser.am include Makefile-st.am include Makefile-tray.am +include Makefile-gvc.am gnome_shell_cflags = \ $(MUTTER_PLUGIN_CFLAGS) \ @@ -190,11 +191,12 @@ libgnome_shell_la_LIBADD = \ $(LIBGNOMEUI_LIBS) \ libst-1.0.la \ libgdmuser-1.0.la \ - libtray.la + libtray.la \ + libgvc.la libgnome_shell_la_CPPFLAGS = $(gnome_shell_cflags) typelibdir = $(pkglibdir) -typelib_DATA = Shell-0.1.typelib St-1.0.typelib Gdm-1.0.typelib +typelib_DATA = Shell-0.1.typelib St-1.0.typelib Gdm-1.0.typelib Gvc-1.0.typelib Shell-0.1.gir: $(mutter) $(G_IR_SCANNER) St-1.0.gir libgnome-shell.la Makefile $(AM_V_GEN) $(G_IR_SCANNER) \ diff --git a/src/gdmuser/gdm-user-manager.c b/src/gdmuser/gdm-user-manager.c index 5f6bf85c7..b60e5a6d0 100644 --- a/src/gdmuser/gdm-user-manager.c +++ b/src/gdmuser/gdm-user-manager.c @@ -51,23 +51,20 @@ #define GDM_USER_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_USER_MANAGER, GdmUserManagerPrivate)) #define CK_NAME "org.freedesktop.ConsoleKit" -#define CK_PATH "/org/freedesktop/ConsoleKit" -#define CK_INTERFACE "org.freedesktop.ConsoleKit" #define CK_MANAGER_PATH "/org/freedesktop/ConsoleKit/Manager" #define CK_MANAGER_INTERFACE "org.freedesktop.ConsoleKit.Manager" #define CK_SEAT_INTERFACE "org.freedesktop.ConsoleKit.Seat" #define CK_SESSION_INTERFACE "org.freedesktop.ConsoleKit.Session" +#define GDM_DBUS_TYPE_G_OBJECT_PATH_ARRAY (dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_OBJECT_PATH)) + /* Prefs Defaults */ -#define DEFAULT_ALLOW_ROOT TRUE -#define DEFAULT_MAX_ICON_SIZE 128 -#define DEFAULT_USER_MAX_FILE 65536 #ifdef __sun -#define DEFAULT_MINIMAL_UID 100 +#define FALLBACK_MINIMAL_UID 100 #else -#define DEFAULT_MINIMAL_UID 500 +#define FALLBACK_MINIMAL_UID 500 #endif #ifndef _PATH_SHELLS @@ -75,55 +72,134 @@ #endif #define PATH_PASSWD "/etc/passwd" -#define DEFAULT_GLOBAL_FACE_DIR DATADIR "/faces" -#define DEFAULT_USER_ICON "stock_person" -#define DEFAULT_EXCLUDE { "bin", \ - "root", \ - "daemon", \ - "adm", \ - "lp", \ - "sync", \ - "shutdown", \ - "halt", \ - "mail", \ - "news", \ - "uucp", \ - "operator", \ - "nobody", \ - "gdm", \ - "postgres", \ - "pvm", \ - "rpm", \ - "nfsnobody", \ - "pcap", \ - NULL } +#ifndef GDM_USERNAME +#define GDM_USERNAME "gdm" +#endif + +#define RELOAD_PASSWD_THROTTLE_SECS 5 + +/* approximately two months */ +#define LOGIN_FREQUENCY_TIME_WINDOW_SECS (60 * 24 * 60 * 60) + +#define ACCOUNTS_NAME "org.freedesktop.Accounts" +#define ACCOUNTS_PATH "/org/freedesktop/Accounts" +#define ACCOUNTS_INTERFACE "org.freedesktop.Accounts" + +typedef enum { + GDM_USER_MANAGER_SEAT_STATE_UNLOADED = 0, + GDM_USER_MANAGER_SEAT_STATE_GET_SESSION_ID, + GDM_USER_MANAGER_SEAT_STATE_GET_ID, + GDM_USER_MANAGER_SEAT_STATE_GET_PROXY, + GDM_USER_MANAGER_SEAT_STATE_LOADED, +} GdmUserManagerSeatState; + +typedef struct +{ + GdmUserManagerSeatState state; + char *id; + char *session_id; + union { + DBusGProxyCall *get_current_session_call; + DBusGProxyCall *get_seat_id_call; + }; + + DBusGProxy *proxy; +} GdmUserManagerSeat; + +typedef enum { + GDM_USER_MANAGER_NEW_SESSION_STATE_UNLOADED = 0, + GDM_USER_MANAGER_NEW_SESSION_STATE_GET_PROXY, + GDM_USER_MANAGER_NEW_SESSION_STATE_GET_UID, + GDM_USER_MANAGER_NEW_SESSION_STATE_GET_X11_DISPLAY, + GDM_USER_MANAGER_NEW_SESSION_STATE_MAYBE_ADD, + GDM_USER_MANAGER_NEW_SESSION_STATE_LOADED, +} GdmUserManagerNewSessionState; + +typedef struct +{ + GdmUserManager *manager; + GdmUserManagerNewSessionState state; + char *id; + + union { + DBusGProxyCall *get_unix_user_call; + DBusGProxyCall *get_x11_display_call; + }; + + DBusGProxy *proxy; + + uid_t uid; + char *x11_display; +} GdmUserManagerNewSession; + +typedef enum { + GDM_USER_MANAGER_GET_USER_STATE_UNFETCHED = 0, + GDM_USER_MANAGER_GET_USER_STATE_WAIT_FOR_LOADED, + GDM_USER_MANAGER_GET_USER_STATE_ASK_ACCOUNTS_SERVICE, + GDM_USER_MANAGER_GET_USER_STATE_FETCHED +} GdmUserManagerGetUserState; + +typedef struct +{ + GdmUserManager *manager; + GdmUserManagerGetUserState state; + GdmUser *user; + char *username; + char *object_path; + + DBusGProxyCall *call; +} GdmUserManagerFetchUserRequest; struct GdmUserManagerPrivate { - GHashTable *users; + GHashTable *users_by_name; + GHashTable *users_by_object_path; GHashTable *sessions; - GHashTable *exclusions; GHashTable *shells; DBusGConnection *connection; - DBusGProxy *seat_proxy; - char *seat_id; + DBusGProxyCall *get_sessions_call; + DBusGProxy *accounts_proxy; + + GdmUserManagerSeat seat; + + GSList *new_sessions; + GSList *new_users; + GSList *fetch_user_requests; GFileMonitor *passwd_monitor; GFileMonitor *shells_monitor; - guint reload_id; - guint ck_history_id; + GSList *exclude_usernames; + GSList *include_usernames; + gboolean include_all; - guint8 users_dirty : 1; + gboolean load_passwd_pending; + + guint load_id; + guint reload_passwd_id; + guint ck_history_id; + guint ck_history_watchdog_id; + GPid ck_history_pid; + + gboolean is_loaded; + gboolean has_multiple_users; + gboolean listing_cached_users; +}; + +enum { + PROP_0, + PROP_INCLUDE_ALL, + PROP_INCLUDE_USERNAMES_LIST, + PROP_EXCLUDE_USERNAMES_LIST, + PROP_IS_LOADED, + PROP_HAS_MULTIPLE_USERS }; enum { - LOADING_USERS, - USERS_LOADED, USER_ADDED, USER_REMOVED, USER_IS_LOGGED_IN_CHANGED, - USER_LOGIN_FREQUENCY_CHANGED, + USER_CHANGED, LAST_SIGNAL }; @@ -133,6 +209,28 @@ static void gdm_user_manager_class_init (GdmUserManagerClass *klass); static void gdm_user_manager_init (GdmUserManager *user_manager); static void gdm_user_manager_finalize (GObject *object); +static void load_users_manually (GdmUserManager *manager); +static void monitor_local_users (GdmUserManager *manager); +static void load_seat_incrementally (GdmUserManager *manager); +static void unload_seat (GdmUserManager *manager); +static void load_users (GdmUserManager *manager); +static void queue_load_seat_and_users (GdmUserManager *manager); +static void monitor_local_users (GdmUserManager *manager); + +static void load_new_session_incrementally (GdmUserManagerNewSession *new_session); +static void set_is_loaded (GdmUserManager *manager, gboolean is_loaded); + +static void on_new_user_loaded (GdmUser *user, + GParamSpec *pspec, + GdmUserManager *manager); +static void give_up_and_fetch_user_locally (GdmUserManager *manager, + GdmUserManagerFetchUserRequest *request); +static void fetch_user_locally (GdmUserManager *manager, + GdmUser *user, + const char *username); +static void fetch_user_incrementally (GdmUserManagerFetchUserRequest *request); + +static void maybe_set_is_loaded (GdmUserManager *manager); static gpointer user_manager_object = NULL; G_DEFINE_TYPE (GdmUserManager, gdm_user_manager, G_TYPE_OBJECT) @@ -156,76 +254,15 @@ start_new_login_session (GdmUserManager *manager) res = g_spawn_command_line_async ("gdmflexiserver -s", &error); if (! res) { - g_warning ("Unable to start new login: %s", error->message); - g_error_free (error); - } - - return res; -} - -/* needs to stay in sync with gdm-slave */ -static char * -_get_primary_user_session_id (GdmUserManager *manager, - GdmUser *user) -{ - gboolean res; - gboolean can_activate_sessions; - GError *error; - GList *sessions; - GList *l; - char *primary_ssid; - - if (manager->priv->seat_id == NULL || manager->priv->seat_id[0] == '\0') { - g_debug ("GdmUserManager: display seat id is not set; can't switch sessions"); - return NULL; - } - - primary_ssid = NULL; - sessions = NULL; - - g_debug ("GdmUserManager: checking if seat can activate sessions"); - - error = NULL; - res = dbus_g_proxy_call (manager->priv->seat_proxy, - "CanActivateSessions", - &error, - G_TYPE_INVALID, - G_TYPE_BOOLEAN, &can_activate_sessions, - G_TYPE_INVALID); - if (! res) { - g_warning ("unable to determine if seat can activate sessions: %s", - error->message); - g_error_free (error); - goto out; - } - - if (! can_activate_sessions) { - g_debug ("GdmUserManager: seat is unable to activate sessions"); - goto out; - } - - sessions = gdm_user_get_sessions (user); - if (sessions == NULL) { - g_warning ("unable to determine sessions for user: %s", - gdm_user_get_user_name (user)); - goto out; - } - - for (l = sessions; l != NULL; l = l->next) { - const char *ssid; - - ssid = l->data; - - /* FIXME: better way to choose? */ - if (ssid != NULL) { - primary_ssid = g_strdup (ssid); - break; + if (error != NULL) { + g_warning ("Unable to start new login: %s", error->message); + g_error_free (error); + } else { + g_warning ("Unable to start new login"); } } - out: - - return primary_ssid; + return res; } static gboolean @@ -312,8 +349,12 @@ session_is_login_window (GdmUserManager *manager, G_TYPE_STRING, &session_type, G_TYPE_INVALID); if (! res) { - g_debug ("Failed to identify the session type: %s", error->message); - g_error_free (error); + if (error != NULL) { + g_debug ("GdmUserManager: Failed to identify the session type: %s", error->message); + g_error_free (error); + } else { + g_debug ("GdmUserManager: Failed to identify the session type"); + } goto out; } @@ -341,46 +382,36 @@ _get_login_window_session_id (GdmUserManager *manager) char *primary_ssid; int i; - if (manager->priv->seat_id == NULL || manager->priv->seat_id[0] == '\0') { - g_debug ("GdmUserManager: display seat id is not set; can't switch sessions"); + if (manager->priv->seat.id == NULL || manager->priv->seat.id[0] == '\0') { + g_debug ("GdmUserManager: display seat ID is not set; can't switch sessions"); return NULL; } primary_ssid = NULL; sessions = NULL; - g_debug ("GdmSlave: checking if seat can activate sessions"); - - error = NULL; - res = dbus_g_proxy_call (manager->priv->seat_proxy, - "CanActivateSessions", - &error, - G_TYPE_INVALID, - G_TYPE_BOOLEAN, &can_activate_sessions, - G_TYPE_INVALID); - if (! res) { - g_warning ("unable to determine if seat can activate sessions: %s", - error->message); - g_error_free (error); - goto out; - } + can_activate_sessions = gdm_user_manager_can_switch (manager); if (! can_activate_sessions) { - g_debug ("GdmSlave: seat is unable to activate sessions"); + g_debug ("GdmUserManager: seat is unable to activate sessions"); goto out; } error = NULL; - res = dbus_g_proxy_call (manager->priv->seat_proxy, + res = dbus_g_proxy_call (manager->priv->seat.proxy, "GetSessions", &error, G_TYPE_INVALID, dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_OBJECT_PATH), &sessions, G_TYPE_INVALID); if (! res) { - g_warning ("unable to determine sessions for user: %s", - error->message); - g_error_free (error); + if (error != NULL) { + g_warning ("unable to determine sessions for user: %s", + error->message); + g_error_free (error); + } else { + g_warning ("unable to determine sessions for user"); + } goto out; } @@ -410,6 +441,7 @@ gdm_user_manager_goto_login_session (GdmUserManager *manager) char *ssid; g_return_val_if_fail (GDM_IS_USER_MANAGER (manager), FALSE); + g_return_val_if_fail (manager->priv->is_loaded, FALSE); ret = FALSE; @@ -418,7 +450,7 @@ gdm_user_manager_goto_login_session (GdmUserManager *manager) ssid = _get_login_window_session_id (manager); if (ssid != NULL) { - res = activate_session_id (manager, manager->priv->seat_id, ssid); + res = activate_session_id (manager, manager->priv->seat.id, ssid); if (res) { ret = TRUE; } @@ -434,25 +466,74 @@ gdm_user_manager_goto_login_session (GdmUserManager *manager) return ret; } +gboolean +gdm_user_manager_can_switch (GdmUserManager *manager) +{ + gboolean res; + gboolean can_activate_sessions; + GError *error; + + if (!manager->priv->is_loaded) { + g_debug ("GdmUserManager: Unable to switch sessions until fully loaded"); + return FALSE; + } + + if (manager->priv->seat.id == NULL || manager->priv->seat.id[0] == '\0') { + g_debug ("GdmUserManager: display seat ID is not set; can't switch sessions"); + return FALSE; + } + + g_debug ("GdmUserManager: checking if seat can activate sessions"); + + error = NULL; + res = dbus_g_proxy_call (manager->priv->seat.proxy, + "CanActivateSessions", + &error, + G_TYPE_INVALID, + G_TYPE_BOOLEAN, &can_activate_sessions, + G_TYPE_INVALID); + if (! res) { + if (error != NULL) { + g_warning ("unable to determine if seat can activate sessions: %s", + error->message); + g_error_free (error); + } else { + g_warning ("unable to determine if seat can activate sessions"); + } + return FALSE; + } + + return can_activate_sessions; +} + gboolean gdm_user_manager_activate_user_session (GdmUserManager *manager, GdmUser *user) { gboolean ret; - char *ssid; + const char *ssid; gboolean res; + gboolean can_activate_sessions; g_return_val_if_fail (GDM_IS_USER_MANAGER (manager), FALSE); g_return_val_if_fail (GDM_IS_USER (user), FALSE); + g_return_val_if_fail (manager->priv->is_loaded, FALSE); ret = FALSE; - ssid = _get_primary_user_session_id (manager, user); + can_activate_sessions = gdm_user_manager_can_switch (manager); + + if (! can_activate_sessions) { + g_debug ("GdmUserManager: seat is unable to activate sessions"); + goto out; + } + + ssid = gdm_user_get_primary_session_id (user); if (ssid == NULL) { goto out; } - res = activate_session_id (manager, manager->priv->seat_id, ssid); + res = activate_session_id (manager, manager->priv->seat.id, ssid); if (! res) { g_debug ("GdmUserManager: unable to activate session: %s", ssid); goto out; @@ -469,6 +550,10 @@ on_user_sessions_changed (GdmUser *user, { guint nsessions; + if (! manager->priv->is_loaded) { + return; + } + nsessions = gdm_user_get_num_sessions (user); g_debug ("GdmUserManager: sessions changed user=%s num=%d", @@ -484,330 +569,825 @@ on_user_sessions_changed (GdmUser *user, } static void -on_user_icon_changed (GdmUser *user, - GdmUserManager *manager) +on_user_changed (GdmUser *user, + GdmUserManager *manager) { - g_debug ("GdmUserManager: user icon changed"); + if (manager->priv->is_loaded) { + g_debug ("GdmUserManager: user changed"); + g_signal_emit (manager, signals[USER_CHANGED], 0, user); + } } -static char * -get_seat_id_for_session (DBusGConnection *connection, - const char *session_id) +static void +on_get_seat_id_finished (DBusGProxy *proxy, + DBusGProxyCall *call, + GdmUserManager *manager) { - DBusGProxy *proxy; - GError *error; - char *seat_id; - gboolean res; + GError *error; + char *seat_id; + gboolean res; - proxy = NULL; + g_assert (manager->priv->seat.get_seat_id_call == call); + + error = NULL; seat_id = NULL; + res = dbus_g_proxy_end_call (proxy, + call, + &error, + DBUS_TYPE_G_OBJECT_PATH, + &seat_id, + G_TYPE_INVALID); + manager->priv->seat.get_seat_id_call = NULL; + g_object_unref (proxy); - proxy = dbus_g_proxy_new_for_name (connection, - CK_NAME, - session_id, - CK_SESSION_INTERFACE); - if (proxy == NULL) { - g_warning ("Failed to connect to the ConsoleKit session object"); - goto out; - } - - error = NULL; - res = dbus_g_proxy_call (proxy, - "GetSeatId", - &error, - G_TYPE_INVALID, - DBUS_TYPE_G_OBJECT_PATH, &seat_id, - G_TYPE_INVALID); if (! res) { - g_debug ("Failed to identify the current seat: %s", error->message); - g_error_free (error); - } - out: - if (proxy != NULL) { - g_object_unref (proxy); + if (error != NULL) { + g_debug ("Failed to identify the seat of the " + "current session: %s", + error->message); + g_error_free (error); + } else { + g_debug ("Failed to identify the seat of the " + "current session"); + } + unload_seat (manager); + maybe_set_is_loaded (manager); + return; } - return seat_id; + g_debug ("GdmUserManager: Found current seat: %s", seat_id); + + manager->priv->seat.id = seat_id; + manager->priv->seat.state++; + + load_seat_incrementally (manager); } -static char * -get_x11_display_for_session (DBusGConnection *connection, - const char *session_id) +static void +get_seat_id_for_current_session (GdmUserManager *manager) { DBusGProxy *proxy; - GError *error; - char *x11_display; - gboolean res; + DBusGProxyCall *call; - proxy = NULL; - x11_display = NULL; - - proxy = dbus_g_proxy_new_for_name (connection, + proxy = dbus_g_proxy_new_for_name (manager->priv->connection, CK_NAME, - session_id, + manager->priv->seat.session_id, CK_SESSION_INTERFACE); if (proxy == NULL) { g_warning ("Failed to connect to the ConsoleKit session object"); - goto out; + goto failed; } - error = NULL; - res = dbus_g_proxy_call (proxy, - "GetX11Display", - &error, - G_TYPE_INVALID, - G_TYPE_STRING, &x11_display, - G_TYPE_INVALID); - if (! res) { - g_debug ("Failed to identify the x11 display: %s", error->message); - g_error_free (error); + call = dbus_g_proxy_begin_call (proxy, + "GetSeatId", + (DBusGProxyCallNotify) + on_get_seat_id_finished, + manager, + NULL, + G_TYPE_INVALID); + if (call == NULL) { + g_warning ("GdmUserManager: failed to make GetSeatId call"); + goto failed; } - out: + + manager->priv->seat.get_seat_id_call = call; + + return; + +failed: if (proxy != NULL) { g_object_unref (proxy); } - return x11_display; + unload_seat (manager); +} + +static gint +match_name_cmpfunc (gconstpointer a, + gconstpointer b) +{ + return g_strcmp0 ((char *) a, + (char *) b); } static gboolean -maybe_add_session_for_user (GdmUserManager *manager, - GdmUser *user, - const char *ssid) +username_in_exclude_list (GdmUserManager *manager, + const char *username) { - char *sid; - char *x11_display; - gboolean ret; + GSList *found; + gboolean ret = FALSE; - ret = FALSE; - sid = NULL; - x11_display = NULL; - - /* skip if on another seat */ - sid = get_seat_id_for_session (manager->priv->connection, ssid); - if (sid == NULL - || manager->priv->seat_id == NULL - || strcmp (sid, manager->priv->seat_id) != 0) { - g_debug ("GdmUserManager: not adding session on other seat: %s", ssid); - goto out; + /* always exclude the "gdm" user. */ + if (username == NULL || (strcmp (username, GDM_USERNAME) == 0)) { + return TRUE; } - /* skip if doesn't have an x11 display */ - x11_display = get_x11_display_for_session (manager->priv->connection, ssid); - if (x11_display == NULL || x11_display[0] == '\0') { - g_debug ("GdmUserManager: not adding session without a x11 display: %s", ssid); - goto out; + if (manager->priv->exclude_usernames != NULL) { + found = g_slist_find_custom (manager->priv->exclude_usernames, + username, + match_name_cmpfunc); + if (found != NULL) { + ret = TRUE; + } } - if (g_hash_table_lookup (manager->priv->exclusions, gdm_user_get_user_name (user))) { - g_debug ("GdmUserManager: excluding user '%s'", gdm_user_get_user_name (user)); - goto out; - } + return ret; +} +static void +add_session_for_user (GdmUserManager *manager, + GdmUser *user, + const char *ssid) +{ g_hash_table_insert (manager->priv->sessions, g_strdup (ssid), g_strdup (gdm_user_get_user_name (user))); _gdm_user_add_session (user, ssid); g_debug ("GdmUserManager: added session for user: %s", gdm_user_get_user_name (user)); - - ret = TRUE; - - out: - g_free (sid); - g_free (x11_display); - - return ret; } static void -add_sessions_for_user (GdmUserManager *manager, - GdmUser *user) +set_has_multiple_users (GdmUserManager *manager, + gboolean has_multiple_users) { - DBusGProxy *proxy; - GError *error; - gboolean res; - guint32 uid; - GPtrArray *sessions; - int i; - - proxy = dbus_g_proxy_new_for_name (manager->priv->connection, - CK_NAME, - CK_MANAGER_PATH, - CK_MANAGER_INTERFACE); - if (proxy == NULL) { - g_warning ("Failed to connect to the ConsoleKit manager object"); - goto out; - } - - uid = gdm_user_get_uid (user); - - g_debug ("Getting list of sessions for user %u", uid); - - error = NULL; - res = dbus_g_proxy_call (proxy, - "GetSessionsForUnixUser", - &error, - G_TYPE_UINT, uid, - G_TYPE_INVALID, - dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_OBJECT_PATH), - &sessions, - G_TYPE_INVALID); - if (! res) { - g_debug ("Failed to find sessions for user: %s", error->message); - g_error_free (error); - goto out; - } - - g_debug ("Found %d sessions for user %s", sessions->len, gdm_user_get_user_name (user)); - - for (i = 0; i < sessions->len; i++) { - char *ssid; - - ssid = g_ptr_array_index (sessions, i); - maybe_add_session_for_user (manager, user, ssid); - } - - g_ptr_array_foreach (sessions, (GFunc)g_free, NULL); - g_ptr_array_free (sessions, TRUE); - - out: - if (proxy != NULL) { - g_object_unref (proxy); + if (manager->priv->has_multiple_users != has_multiple_users) { + manager->priv->has_multiple_users = has_multiple_users; + g_object_notify (G_OBJECT (manager), "has-multiple-users"); } } static GdmUser * -create_user (GdmUserManager *manager) +create_new_user (GdmUserManager *manager) { GdmUser *user; - user = g_object_new (GDM_TYPE_USER, "manager", manager, NULL); - g_signal_connect (user, - "sessions-changed", - G_CALLBACK (on_user_sessions_changed), - manager); - g_signal_connect (user, - "icon-changed", - G_CALLBACK (on_user_icon_changed), - manager); - return user; + user = g_object_new (GDM_TYPE_USER, NULL); + + manager->priv->new_users = g_slist_prepend (manager->priv->new_users, user); + + g_signal_connect (user, "notify::is-loaded", G_CALLBACK (on_new_user_loaded), manager); + + return g_object_ref (user); } static void add_user (GdmUserManager *manager, GdmUser *user) { - add_sessions_for_user (manager, user); - g_hash_table_insert (manager->priv->users, + const char *object_path; + + g_hash_table_insert (manager->priv->users_by_name, g_strdup (gdm_user_get_user_name (user)), g_object_ref (user)); - g_signal_emit (manager, signals[USER_ADDED], 0, user); + object_path = gdm_user_get_object_path (user); + if (object_path != NULL) { + g_hash_table_insert (manager->priv->users_by_object_path, + (gpointer) object_path, + g_object_ref (user)); + } + + g_signal_connect (user, + "sessions-changed", + G_CALLBACK (on_user_sessions_changed), + manager); + g_signal_connect (user, + "changed", + G_CALLBACK (on_user_changed), + manager); + + if (manager->priv->is_loaded) { + g_signal_emit (manager, signals[USER_ADDED], 0, user); + } + + if (g_hash_table_size (manager->priv->users_by_name) > 1) { + set_has_multiple_users (manager, TRUE); + } +} + +static void +remove_user (GdmUserManager *manager, + GdmUser *user) +{ + g_object_ref (user); + + g_signal_handlers_disconnect_by_func (user, on_user_changed, manager); + g_signal_handlers_disconnect_by_func (user, on_user_sessions_changed, manager); + if (gdm_user_get_object_path (user) != NULL) { + g_hash_table_remove (manager->priv->users_by_object_path, gdm_user_get_object_path (user)); + } + g_hash_table_remove (manager->priv->users_by_name, gdm_user_get_user_name (user)); + + if (manager->priv->is_loaded) { + g_signal_emit (manager, signals[USER_REMOVED], 0, user); + } + + g_object_unref (user); + + if (g_hash_table_size (manager->priv->users_by_name) > 1) { + set_has_multiple_users (manager, FALSE); + } +} + +static void +on_new_user_loaded (GdmUser *user, + GParamSpec *pspec, + GdmUserManager *manager) +{ + const char *username; + GdmUser *old_user; + + if (!gdm_user_is_loaded (user)) { + return; + } + + g_signal_handlers_disconnect_by_func (user, on_new_user_loaded, manager); + manager->priv->new_users = g_slist_remove (manager->priv->new_users, + user); + + username = gdm_user_get_user_name (user); + + if (username == NULL) { + const char *object_path; + + object_path = gdm_user_get_object_path (user); + + if (object_path != NULL) { + g_warning ("GdmUserManager: user has no username " + "(object path: %s, uid: %lu)", + object_path, gdm_user_get_uid (user)); + } else { + g_warning ("GdmUserManager: user has no username (uid: %lu)", + gdm_user_get_uid (user)); + } + g_object_unref (user); + return; + } + + if (username_in_exclude_list (manager, username)) { + g_debug ("GdmUserManager: excluding user '%s'", username); + g_object_unref (user); + return; + } + + old_user = g_hash_table_lookup (manager->priv->users_by_name, username); + + /* If username got added earlier by a different means, trump it now. + */ + if (old_user != NULL) { + remove_user (manager, old_user); + } + + add_user (manager, user); + g_object_unref (user); + + if (manager->priv->new_users == NULL) { + set_is_loaded (manager, TRUE); + } } static GdmUser * -add_new_user_for_pwent (GdmUserManager *manager, - struct passwd *pwent) +add_new_user_for_object_path (const char *object_path, + GdmUserManager *manager) { GdmUser *user; - g_debug ("Creating new user"); + user = g_hash_table_lookup (manager->priv->users_by_object_path, object_path); - user = create_user (manager); - _gdm_user_update (user, pwent); - - add_user (manager, user); + if (user != NULL) { + return user; + } + user = create_new_user (manager); + _gdm_user_update_from_object_path (user, object_path); return user; } -static char * -get_current_seat_id (DBusGConnection *connection) +static void +on_new_user_in_accounts_service (DBusGProxy *proxy, + const char *object_path, + gpointer user_data) +{ + GdmUserManager *manager = GDM_USER_MANAGER (user_data); + + add_new_user_for_object_path (object_path, manager); +} + +static void +on_user_removed_in_accounts_service (DBusGProxy *proxy, + const char *object_path, + gpointer user_data) +{ + GdmUserManager *manager = GDM_USER_MANAGER (user_data); + GdmUser *user; + + user = g_hash_table_lookup (manager->priv->users_by_object_path, object_path); + + manager->priv->new_users = g_slist_remove (manager->priv->new_users, user); + + remove_user (manager, user); +} + +static void +on_get_current_session_finished (DBusGProxy *proxy, + DBusGProxyCall *call, + GdmUserManager *manager) +{ + GError *error; + char *session_id; + gboolean res; + + g_assert (manager->priv->seat.get_current_session_call == call); + g_assert (manager->priv->seat.state == GDM_USER_MANAGER_SEAT_STATE_GET_SESSION_ID); + + error = NULL; + session_id = NULL; + res = dbus_g_proxy_end_call (proxy, + call, + &error, + DBUS_TYPE_G_OBJECT_PATH, + &session_id, + G_TYPE_INVALID); + manager->priv->seat.get_current_session_call = NULL; + g_object_unref (proxy); + + if (! res) { + if (error != NULL) { + g_debug ("Failed to identify the current session: %s", + error->message); + g_error_free (error); + } else { + g_debug ("Failed to identify the current session"); + } + unload_seat (manager); + maybe_set_is_loaded (manager); + return; + } + + manager->priv->seat.session_id = session_id; + manager->priv->seat.state++; + + load_seat_incrementally (manager); +} + +static void +get_current_session_id (GdmUserManager *manager) { DBusGProxy *proxy; - GError *error; - char *session_id; - char *seat_id; - gboolean res; + DBusGProxyCall *call; - proxy = NULL; - session_id = NULL; - seat_id = NULL; - - proxy = dbus_g_proxy_new_for_name (connection, + proxy = dbus_g_proxy_new_for_name (manager->priv->connection, CK_NAME, CK_MANAGER_PATH, CK_MANAGER_INTERFACE); if (proxy == NULL) { g_warning ("Failed to connect to the ConsoleKit manager object"); - goto out; + goto failed; } - error = NULL; - res = dbus_g_proxy_call (proxy, - "GetCurrentSession", - &error, - G_TYPE_INVALID, - DBUS_TYPE_G_OBJECT_PATH, - &session_id, - G_TYPE_INVALID); - if (! res) { - g_debug ("Failed to identify the current session: %s", error->message); - g_error_free (error); - goto out; + call = dbus_g_proxy_begin_call (proxy, + "GetCurrentSession", + (DBusGProxyCallNotify) + on_get_current_session_finished, + manager, + NULL, + G_TYPE_INVALID); + if (call == NULL) { + g_warning ("GdmUserManager: failed to make GetCurrentSession call"); + goto failed; } - seat_id = get_seat_id_for_session (connection, session_id); + manager->priv->seat.get_current_session_call = call; - out: + return; + +failed: if (proxy != NULL) { g_object_unref (proxy); } - g_free (session_id); - return seat_id; + unload_seat (manager); } -static gboolean -get_uid_from_session_id (GdmUserManager *manager, - const char *session_id, - uid_t *uidp) +static void +unload_new_session (GdmUserManagerNewSession *new_session) { - DBusGProxy *proxy; - GError *error; - guint uid; - gboolean res; + GdmUserManager *manager; + + manager = new_session->manager; + + manager->priv->new_sessions = g_slist_remove (manager->priv->new_sessions, + new_session); + + if (new_session->proxy != NULL) { + g_object_unref (new_session->proxy); + } + + g_free (new_session->x11_display); + g_free (new_session->id); + + g_slice_free (GdmUserManagerNewSession, new_session); +} + +static void +get_proxy_for_new_session (GdmUserManagerNewSession *new_session) +{ + GdmUserManager *manager; + DBusGProxy *proxy; + + manager = new_session->manager; proxy = dbus_g_proxy_new_for_name (manager->priv->connection, CK_NAME, - session_id, + new_session->id, CK_SESSION_INTERFACE); if (proxy == NULL) { - g_warning ("Failed to connect to the ConsoleKit session object"); - return FALSE; + g_warning ("Failed to connect to the ConsoleKit '%s' object", + new_session->id); + unload_new_session (new_session); + return; } + new_session->proxy = proxy; + new_session->state++; + + load_new_session_incrementally (new_session); +} + +static void +on_get_unix_user_finished (DBusGProxy *proxy, + DBusGProxyCall *call, + GdmUserManagerNewSession *new_session) +{ + GdmUserManager *manager; + GError *error; + guint uid; + gboolean res; + + manager = new_session->manager; + + g_assert (new_session->get_unix_user_call == call); + error = NULL; - res = dbus_g_proxy_call (proxy, - "GetUnixUser", - &error, - G_TYPE_INVALID, - G_TYPE_UINT, &uid, - G_TYPE_INVALID); - g_object_unref (proxy); + + uid = (guint) -1; + res = dbus_g_proxy_end_call (proxy, + call, + &error, + G_TYPE_UINT, &uid, + G_TYPE_INVALID); + new_session->get_unix_user_call = NULL; if (! res) { - g_warning ("Failed to query the session: %s", error->message); + if (error != NULL) { + g_debug ("Failed to get uid of session '%s': %s", + new_session->id, error->message); + g_error_free (error); + } else { + g_debug ("Failed to get uid of session '%s'", + new_session->id); + } + unload_new_session (new_session); + return; + } + + g_debug ("GdmUserManager: Found uid of session '%s': %u", + new_session->id, uid); + + new_session->uid = (uid_t) uid; + new_session->state++; + + load_new_session_incrementally (new_session); +} + +static void +get_uid_for_new_session (GdmUserManagerNewSession *new_session) +{ + DBusGProxyCall *call; + + g_assert (new_session->proxy != NULL); + + call = dbus_g_proxy_begin_call (new_session->proxy, + "GetUnixUser", + (DBusGProxyCallNotify) + on_get_unix_user_finished, + new_session, + NULL, + G_TYPE_INVALID); + if (call == NULL) { + g_warning ("GdmUserManager: failed to make GetUnixUser call"); + goto failed; + } + + new_session->get_unix_user_call = call; + return; + +failed: + unload_new_session (new_session); +} + +static void +on_find_user_by_name_finished (DBusGProxy *proxy, + DBusGProxyCall *call, + GdmUserManagerFetchUserRequest *request) +{ + GdmUserManager *manager; + GError *error; + char *object_path; + gboolean res; + + g_assert (request->call == call); + + error = NULL; + object_path = NULL; + manager = request->manager; + res = dbus_g_proxy_end_call (manager->priv->accounts_proxy, + call, + &error, + DBUS_TYPE_G_OBJECT_PATH, + &object_path, + G_TYPE_INVALID); + if (! res) { + if (error != NULL) { + g_debug ("GdmUserManager: Failed to find user %s: %s", + request->username, error->message); + g_error_free (error); + } else { + g_debug ("GdmUserManager: Failed to find user %s", + request->username); + } + give_up_and_fetch_user_locally (manager, request); + return; + } + + g_debug ("GdmUserManager: Found object path of user '%s': %s", + request->username, object_path); + request->object_path = object_path; + request->state++; + + fetch_user_incrementally (request); +} + +static void +find_user_in_accounts_service (GdmUserManager *manager, + GdmUserManagerFetchUserRequest *request) +{ + DBusGProxyCall *call; + + g_debug ("GdmUserManager: Looking for user %s in accounts service", + request->username); + + g_assert (manager->priv->accounts_proxy != NULL); + + call = dbus_g_proxy_begin_call (manager->priv->accounts_proxy, + "FindUserByName", + (DBusGProxyCallNotify) + on_find_user_by_name_finished, + request, + NULL, + G_TYPE_STRING, + request->username, + G_TYPE_INVALID); + + if (call == NULL) { + g_warning ("GdmUserManager: failed to make FindUserByName('%s') call", + request->username); + goto failed; + } + + request->call = call; + return; + +failed: + give_up_and_fetch_user_locally (manager, request); +} + +static void +set_is_loaded (GdmUserManager *manager, + gboolean is_loaded) +{ + if (manager->priv->is_loaded != is_loaded) { + manager->priv->is_loaded = is_loaded; + g_object_notify (G_OBJECT (manager), "is-loaded"); + } +} + +static void +on_list_cached_users_finished (DBusGProxy *proxy, + DBusGProxyCall *call_id, + gpointer data) +{ + GdmUserManager *manager = data; + GError *error = NULL; + GPtrArray *paths; + + manager->priv->listing_cached_users = FALSE; + if (!dbus_g_proxy_end_call (proxy, + call_id, + &error, + dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_OBJECT_PATH), &paths, + G_TYPE_INVALID)) { + g_debug ("GdmUserManager: ListCachedUsers failed: %s", error->message); g_error_free (error); - return FALSE; + + g_object_unref (manager->priv->accounts_proxy); + manager->priv->accounts_proxy = NULL; + + load_users_manually (manager); + + return; } - if (uidp != NULL) { - *uidp = (uid_t) uid; + g_ptr_array_foreach (paths, (GFunc)add_new_user_for_object_path, manager); + + g_ptr_array_foreach (paths, (GFunc)g_free, NULL); + g_ptr_array_free (paths, TRUE); + + /* Add users who are specifically included */ + if (manager->priv->include_usernames != NULL) { + GSList *l; + + for (l = manager->priv->include_usernames; l != NULL; l = l->next) { + GdmUser *user; + + g_debug ("GdmUserManager: Adding included user %s", (char *)l->data); + /* + * The call to gdm_user_manager_get_user will add the user if it is + * valid and not already in the hash. + */ + user = gdm_user_manager_get_user (manager, l->data); + if (user == NULL) { + g_debug ("GdmUserManager: unable to lookup user '%s'", (char *)l->data); + } + } + } +} + +static void +on_get_x11_display_finished (DBusGProxy *proxy, + DBusGProxyCall *call, + GdmUserManagerNewSession *new_session) +{ + GError *error; + char *x11_display; + gboolean res; + + g_assert (new_session->get_x11_display_call == call); + + error = NULL; + x11_display = NULL; + res = dbus_g_proxy_end_call (proxy, + call, + &error, + G_TYPE_STRING, + &x11_display, + G_TYPE_INVALID); + new_session->get_x11_display_call = NULL; + + if (! res) { + if (error != NULL) { + g_debug ("Failed to get the x11 display of session '%s': %s", + new_session->id, error->message); + g_error_free (error); + } else { + g_debug ("Failed to get the x11 display of session '%s'", + new_session->id); + } + unload_new_session (new_session); + return; } - return TRUE; + g_debug ("GdmUserManager: Found x11 display of session '%s': %s", + new_session->id, x11_display); + + new_session->x11_display = x11_display; + new_session->state++; + + load_new_session_incrementally (new_session); +} + +static void +get_x11_display_for_new_session (GdmUserManagerNewSession *new_session) +{ + DBusGProxyCall *call; + + g_assert (new_session->proxy != NULL); + + call = dbus_g_proxy_begin_call (new_session->proxy, + "GetX11Display", + (DBusGProxyCallNotify) + on_get_x11_display_finished, + new_session, + NULL, + G_TYPE_INVALID); + if (call == NULL) { + g_warning ("GdmUserManager: failed to make GetX11Display call"); + goto failed; + } + + new_session->get_x11_display_call = call; + return; + +failed: + unload_new_session (new_session); +} + +static gboolean +get_pwent_for_name (const char *name, + struct passwd **pwentp) +{ + struct passwd *pwent; + + do { + errno = 0; + pwent = getpwnam (name); + } while (pwent == NULL && errno == EINTR); + + if (pwentp != NULL) { + *pwentp = pwent; + } + + return (pwent != NULL); +} + +static gboolean +get_pwent_for_uid (uid_t uid, + struct passwd **pwentp) +{ + struct passwd *pwent; + + do { + errno = 0; + pwent = getpwuid (uid); + } while (pwent == NULL && errno == EINTR); + + if (pwentp != NULL) { + *pwentp = pwent; + } + + return (pwent != NULL); +} + +static void +maybe_add_new_session (GdmUserManagerNewSession *new_session) +{ + GdmUserManager *manager; + struct passwd *pwent; + GdmUser *user; + + manager = GDM_USER_MANAGER (new_session->manager); + + errno = 0; + get_pwent_for_uid (new_session->uid, &pwent); + if (pwent == NULL) { + g_warning ("Unable to lookup user ID %d: %s", + (int) new_session->uid, g_strerror (errno)); + goto failed; + } + + /* check exclusions up front */ + if (username_in_exclude_list (manager, pwent->pw_name)) { + g_debug ("GdmUserManager: excluding user '%s'", pwent->pw_name); + goto failed; + } + + user = gdm_user_manager_get_user (manager, pwent->pw_name); + if (user == NULL) { + return; + } + + add_session_for_user (manager, user, new_session->id); + + /* if we haven't yet gotten the login frequency + then at least add one because the session exists */ + if (gdm_user_get_login_frequency (user) == 0) { + _gdm_user_update_login_frequency (user, 1); + } + + manager->priv->seat.state = GDM_USER_MANAGER_SEAT_STATE_LOADED; + unload_new_session (new_session); + return; + +failed: + unload_new_session (new_session); +} + +static void +load_new_session (GdmUserManager *manager, + const char *session_id) +{ + GdmUserManagerNewSession *new_session; + + new_session = g_slice_new0 (GdmUserManagerNewSession); + + new_session->manager = manager; + new_session->id = g_strdup (session_id); + new_session->state = GDM_USER_MANAGER_NEW_SESSION_STATE_UNLOADED + 1; + + manager->priv->new_sessions = g_slist_prepend (manager->priv->new_sessions, + new_session); + load_new_session_incrementally (new_session); } static void @@ -815,54 +1395,22 @@ seat_session_added (DBusGProxy *seat_proxy, const char *session_id, GdmUserManager *manager) { - uid_t uid; - gboolean res; - struct passwd *pwent; - GdmUser *user; - gboolean is_new; + g_debug ("GdmUserManager: Session added: %s", session_id); - g_debug ("Session added: %s", session_id); + load_new_session (manager, session_id); +} - res = get_uid_from_session_id (manager, session_id, &uid); - if (! res) { - g_warning ("Unable to lookup user for session"); - return; - } +static gint +match_new_session_cmpfunc (gconstpointer a, + gconstpointer b) +{ + GdmUserManagerNewSession *new_session; + const char *session_id; - errno = 0; - pwent = getpwuid (uid); - if (pwent == NULL) { - g_warning ("Unable to lookup user id %d: %s", (int)uid, g_strerror (errno)); - return; - } + new_session = (GdmUserManagerNewSession *) a; + session_id = (const char *) b; - /* check exclusions up front */ - if (g_hash_table_lookup (manager->priv->exclusions, pwent->pw_name)) { - g_debug ("GdmUserManager: excluding user '%s'", pwent->pw_name); - return; - } - - user = g_hash_table_lookup (manager->priv->users, pwent->pw_name); - if (user == NULL) { - g_debug ("Creating new user"); - - user = create_user (manager); - _gdm_user_update (user, pwent); - is_new = TRUE; - } else { - is_new = FALSE; - } - - res = maybe_add_session_for_user (manager, user, session_id); - - /* only add the user if we added a session */ - if (is_new) { - if (res) { - add_user (manager, user); - } else { - g_object_unref (user); - } - } + return strcmp (new_session->id, session_id); } static void @@ -870,10 +1418,36 @@ seat_session_removed (DBusGProxy *seat_proxy, const char *session_id, GdmUserManager *manager) { - GdmUser *user; - char *username; + GdmUser *user; + GSList *found; + char *username; - g_debug ("Session removed: %s", session_id); + g_debug ("GdmUserManager: Session removed: %s", session_id); + + found = g_slist_find_custom (manager->priv->new_sessions, + session_id, + match_new_session_cmpfunc); + + if (found != NULL) { + GdmUserManagerNewSession *new_session; + + new_session = (GdmUserManagerNewSession *) found->data; + + if (new_session->state > GDM_USER_MANAGER_NEW_SESSION_STATE_GET_X11_DISPLAY) { + g_debug ("GdmUserManager: New session for uid %d on " + "x11 display %s removed before fully loading", + (int) new_session->uid, new_session->x11_display); + } else if (new_session->state > GDM_USER_MANAGER_NEW_SESSION_STATE_GET_UID) { + g_debug ("GdmUserManager: New session for uid %d " + "removed before fully loading", + (int) new_session->uid); + } else { + g_debug ("GdmUserManager: New session removed " + "before fully loading"); + } + unload_new_session (new_session); + return; + } /* since the session object may already be gone * we can't query CK directly */ @@ -883,7 +1457,7 @@ seat_session_removed (DBusGProxy *seat_proxy, return; } - user = g_hash_table_lookup (manager->priv->users, username); + user = g_hash_table_lookup (manager->priv->users_by_name, username); if (user == NULL) { /* nothing to do */ return; @@ -894,12 +1468,12 @@ seat_session_removed (DBusGProxy *seat_proxy, } static void -on_proxy_destroy (DBusGProxy *proxy, - GdmUserManager *manager) +on_seat_proxy_destroy (DBusGProxy *proxy, + GdmUserManager *manager) { g_debug ("GdmUserManager: seat proxy destroyed"); - manager->priv->seat_proxy = NULL; + manager->priv->seat.proxy = NULL; } static void @@ -908,37 +1482,28 @@ get_seat_proxy (GdmUserManager *manager) DBusGProxy *proxy; GError *error; - g_assert (manager->priv->seat_proxy == NULL); - - error = NULL; - manager->priv->connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error); - if (manager->priv->connection == NULL) { - g_warning ("Failed to connect to the D-Bus daemon: %s", error->message); - g_error_free (error); - return; - } - - manager->priv->seat_id = get_current_seat_id (manager->priv->connection); - if (manager->priv->seat_id == NULL) { - return; - } - - g_debug ("GdmUserManager: Found current seat: %s", manager->priv->seat_id); + g_assert (manager->priv->seat.proxy == NULL); error = NULL; proxy = dbus_g_proxy_new_for_name_owner (manager->priv->connection, CK_NAME, - manager->priv->seat_id, + manager->priv->seat.id, CK_SEAT_INTERFACE, &error); if (proxy == NULL) { - g_warning ("Failed to connect to the ConsoleKit seat object: %s", error->message); - g_error_free (error); + if (error != NULL) { + g_warning ("Failed to connect to the ConsoleKit seat object: %s", + error->message); + g_error_free (error); + } else { + g_warning ("Failed to connect to the ConsoleKit seat object"); + } + unload_seat (manager); return; } - g_signal_connect (proxy, "destroy", G_CALLBACK (on_proxy_destroy), manager); + g_signal_connect (proxy, "destroy", G_CALLBACK (on_seat_proxy_destroy), manager); dbus_g_proxy_add_signal (proxy, "SessionAdded", @@ -958,8 +1523,205 @@ get_seat_proxy (GdmUserManager *manager) G_CALLBACK (seat_session_removed), manager, NULL); - manager->priv->seat_proxy = proxy; + manager->priv->seat.proxy = proxy; + manager->priv->seat.state = GDM_USER_MANAGER_SEAT_STATE_LOADED; +} +static void +unload_seat (GdmUserManager *manager) +{ + manager->priv->seat.state = GDM_USER_MANAGER_SEAT_STATE_UNLOADED; + + if (manager->priv->seat.proxy != NULL) { + g_object_unref (manager->priv->seat.proxy); + manager->priv->seat.proxy = NULL; + } + + g_free (manager->priv->seat.id); + manager->priv->seat.id = NULL; + + g_free (manager->priv->seat.session_id); + manager->priv->seat.session_id = NULL; +} + +static void +get_accounts_proxy (GdmUserManager *manager) +{ + DBusGProxy *proxy; + GError *error; + + g_assert (manager->priv->accounts_proxy == NULL); + + error = NULL; + proxy = dbus_g_proxy_new_for_name (manager->priv->connection, + ACCOUNTS_NAME, + ACCOUNTS_PATH, + ACCOUNTS_INTERFACE); + manager->priv->accounts_proxy = proxy; + + dbus_g_proxy_add_signal (proxy, + "UserAdded", + DBUS_TYPE_G_OBJECT_PATH, + G_TYPE_INVALID); + dbus_g_proxy_add_signal (proxy, + "UserDeleted", + DBUS_TYPE_G_OBJECT_PATH, + G_TYPE_INVALID); + + dbus_g_proxy_connect_signal (proxy, + "UserAdded", + G_CALLBACK (on_new_user_in_accounts_service), + manager, + NULL); + dbus_g_proxy_connect_signal (proxy, + "UserDeleted", + G_CALLBACK (on_user_removed_in_accounts_service), + manager, + NULL); +} + +static void +load_new_session_incrementally (GdmUserManagerNewSession *new_session) +{ + switch (new_session->state) { + case GDM_USER_MANAGER_NEW_SESSION_STATE_GET_PROXY: + get_proxy_for_new_session (new_session); + break; + case GDM_USER_MANAGER_NEW_SESSION_STATE_GET_UID: + get_uid_for_new_session (new_session); + break; + case GDM_USER_MANAGER_NEW_SESSION_STATE_GET_X11_DISPLAY: + get_x11_display_for_new_session (new_session); + break; + case GDM_USER_MANAGER_NEW_SESSION_STATE_MAYBE_ADD: + maybe_add_new_session (new_session); + break; + case GDM_USER_MANAGER_NEW_SESSION_STATE_LOADED: + break; + default: + g_assert_not_reached (); + } +} + +static void +free_fetch_user_request (GdmUserManagerFetchUserRequest *request) +{ + GdmUserManager *manager; + + manager = request->manager; + + manager->priv->fetch_user_requests = g_slist_remove (manager->priv->fetch_user_requests, request); + g_free (request->username); + g_free (request->object_path); + g_slice_free (GdmUserManagerFetchUserRequest, request); +} + +static void +give_up_and_fetch_user_locally (GdmUserManager *manager, + GdmUserManagerFetchUserRequest *request) +{ + + g_debug ("GdmUserManager: account service unavailable, " + "fetching user %s locally", + request->username); + fetch_user_locally (manager, request->user, request->username); + request->state = GDM_USER_MANAGER_GET_USER_STATE_UNFETCHED; +} + +static void +on_user_manager_maybe_ready_for_request (GdmUserManager *manager, + GParamSpec *pspec, + GdmUserManagerFetchUserRequest *request) +{ + if (!manager->priv->is_loaded) { + return; + } + + g_signal_handlers_disconnect_by_func (manager, on_user_manager_maybe_ready_for_request, request); + + request->state++; + fetch_user_incrementally (request); +} + +static void +fetch_user_incrementally (GdmUserManagerFetchUserRequest *request) +{ + GdmUserManager *manager; + + g_debug ("GdmUserManager: finding user %s state %d", + request->username, request->state); + manager = request->manager; + switch (request->state) { + case GDM_USER_MANAGER_GET_USER_STATE_WAIT_FOR_LOADED: + if (manager->priv->is_loaded) { + request->state++; + fetch_user_incrementally (request); + } else { + g_debug ("GdmUserManager: waiting for user manager to load before finding user %s", + request->username); + g_signal_connect (manager, "notify::is-loaded", + G_CALLBACK (on_user_manager_maybe_ready_for_request), request); + + } + break; + + case GDM_USER_MANAGER_GET_USER_STATE_ASK_ACCOUNTS_SERVICE: + if (manager->priv->accounts_proxy == NULL) { + give_up_and_fetch_user_locally (manager, request); + } else { + find_user_in_accounts_service (manager, request); + } + break; + case GDM_USER_MANAGER_GET_USER_STATE_FETCHED: + g_debug ("GdmUserManager: user %s fetched", request->username); + _gdm_user_update_from_object_path (request->user, request->object_path); + break; + case GDM_USER_MANAGER_GET_USER_STATE_UNFETCHED: + g_debug ("GdmUserManager: user %s was not fetched", request->username); + break; + default: + g_assert_not_reached (); + } + + if (request->state == GDM_USER_MANAGER_GET_USER_STATE_FETCHED || + request->state == GDM_USER_MANAGER_GET_USER_STATE_UNFETCHED) { + g_debug ("GdmUserManager: finished handling request for user %s", + request->username); + free_fetch_user_request (request); + } +} + +static void +fetch_user_from_accounts_service (GdmUserManager *manager, + GdmUser *user, + const char *username) +{ + GdmUserManagerFetchUserRequest *request; + + request = g_slice_new0 (GdmUserManagerFetchUserRequest); + + request->manager = manager; + request->username = g_strdup (username); + request->user = user; + request->state = GDM_USER_MANAGER_GET_USER_STATE_UNFETCHED + 1; + + manager->priv->fetch_user_requests = g_slist_prepend (manager->priv->fetch_user_requests, + request); + fetch_user_incrementally (request); +} + +static void +fetch_user_locally (GdmUserManager *manager, + GdmUser *user, + const char *username) +{ + struct passwd *pwent; + + get_pwent_for_name (username, &pwent); + + if (pwent != NULL) { + _gdm_user_update_from_pwent (user, pwent); + } } /** @@ -967,10 +1729,12 @@ get_seat_proxy (GdmUserManager *manager) * @manager: the manager to query. * @username: the login name of the user to get. * - * Retrieves a pointer to the #GdmUser object for the login named @username - * from @manager. This pointer is not a reference, and should not be released. + * Retrieves a pointer to the #GdmUser object for the login @username + * from @manager. Trying to use this object before its + * #GdmUser:is-loaded property is %TRUE will result in undefined + * behavior. * - * Returns: (transfer none): a pointer to a #GdmUser object. + * Returns: (transfer none): #GdmUser object **/ GdmUser * gdm_user_manager_get_user (GdmUserManager *manager, @@ -981,54 +1745,49 @@ gdm_user_manager_get_user (GdmUserManager *manager, g_return_val_if_fail (GDM_IS_USER_MANAGER (manager), NULL); g_return_val_if_fail (username != NULL && username[0] != '\0', NULL); - user = g_hash_table_lookup (manager->priv->users, username); + user = g_hash_table_lookup (manager->priv->users_by_name, username); + /* if we don't have it loaded try to load it now */ if (user == NULL) { - struct passwd *pwent; + user = create_new_user (manager); - pwent = getpwnam (username); - - if (pwent != NULL) { - user = add_new_user_for_pwent (manager, pwent); + if (manager->priv->accounts_proxy != NULL) { + fetch_user_from_accounts_service (manager, user, username); + } else { + fetch_user_locally (manager, user, username); } } return user; } - /** * gdm_user_manager_get_user_by_uid: * @manager: the manager to query. - * @uid: the user id + * @uid: the uid of the user to get. * - * Retrieves a pointer to the #GdmUser object for the login named @username - * from @manager. + * Retrieves a pointer to the #GdmUser object for the uid @uid + * from @manager. Trying to use this object before its + * #GdmUser:is-loaded property is %TRUE will result in undefined + * behavior. * - * Returns: (transfer none): a pointer to a #GdmUser object. - **/ + * Returns: (transfer none): #GdmUser object + */ GdmUser * gdm_user_manager_get_user_by_uid (GdmUserManager *manager, gulong uid) { - GdmUser *user; struct passwd *pwent; g_return_val_if_fail (GDM_IS_USER_MANAGER (manager), NULL); - pwent = getpwuid (uid); + get_pwent_for_uid (uid, &pwent); if (pwent == NULL) { g_warning ("GdmUserManager: unable to lookup uid %d", (int)uid); return NULL; } - user = g_hash_table_lookup (manager->priv->users, pwent->pw_name); - - if (user == NULL) { - user = add_new_user_for_pwent (manager, pwent); - } - - return user; + return gdm_user_manager_get_user (manager, pwent->pw_name); } static void @@ -1043,8 +1802,11 @@ listify_hash_values_hfunc (gpointer key, /** * gdm_user_manager_list_users: + * @manager: a #GdmUserManager * - * Return value: (element-type GdmUser) (transfer container): list of #GdmUser elements + * Get a list of system user accounts + * + * Returns: (element-type GdmUser) (transfer full): List of #GdmUser objects */ GSList * gdm_user_manager_list_users (GdmUserManager *manager) @@ -1054,7 +1816,7 @@ gdm_user_manager_list_users (GdmUserManager *manager) g_return_val_if_fail (GDM_IS_USER_MANAGER (manager), NULL); retval = NULL; - g_hash_table_foreach (manager->priv->users, listify_hash_values_hfunc, &retval); + g_hash_table_foreach (manager->priv->users_by_name, listify_hash_values_hfunc, &retval); return g_slist_sort (retval, (GCompareFunc) gdm_user_collate); } @@ -1102,7 +1864,11 @@ parse_ck_history_line (const char *line, error = NULL; re = g_regex_new ("(?P[0-9a-zA-Z]+)[ ]+(?P[0-9]+)", 0, 0, &error); if (re == NULL) { - g_critical ("%s", error->message); + if (error != NULL) { + g_critical ("%s", error->message); + } else { + g_critical ("Error in regex call"); + } goto out; } @@ -1156,7 +1922,7 @@ process_ck_history_line (GdmUserManager *manager, return; } - if (g_hash_table_lookup (manager->priv->exclusions, username)) { + if (username_in_exclude_list (manager, username)) { g_debug ("GdmUserManager: excluding user '%s'", username); g_free (username); return; @@ -1169,11 +1935,44 @@ process_ck_history_line (GdmUserManager *manager, return; } - g_object_set (user, "login-frequency", frequency, NULL); - g_signal_emit (manager, signals [USER_LOGIN_FREQUENCY_CHANGED], 0, user); + _gdm_user_update_login_frequency (user, frequency); g_free (username); } +static void +maybe_set_is_loaded (GdmUserManager *manager) +{ + if (manager->priv->is_loaded) { + return; + } + + if (manager->priv->ck_history_pid != 0) { + return; + } + + if (manager->priv->load_passwd_pending) { + return; + } + + if (manager->priv->get_sessions_call != NULL) { + return; + } + + if (manager->priv->listing_cached_users) { + return; + } + + /* Don't set is_loaded yet unless the seat is already loaded + * or failed to load. + */ + if (manager->priv->seat.state != GDM_USER_MANAGER_SEAT_STATE_LOADED + && manager->priv->seat.state != GDM_USER_MANAGER_SEAT_STATE_UNLOADED) { + return; + } + + set_is_loaded (manager, TRUE); +} + static gboolean ck_history_watch (GIOChannel *source, GIOCondition condition, @@ -1208,45 +2007,105 @@ ck_history_watch (GIOChannel *source, } if (done) { - g_signal_emit (G_OBJECT (manager), signals[USERS_LOADED], 0); - manager->priv->ck_history_id = 0; + if (manager->priv->ck_history_watchdog_id != 0) { + g_source_remove (manager->priv->ck_history_watchdog_id); + manager->priv->ck_history_watchdog_id = 0; + } + manager->priv->ck_history_pid = 0; + + maybe_set_is_loaded (manager); + return FALSE; } return TRUE; } -static void -reload_ck_history (GdmUserManager *manager) +static int +signal_pid (int pid, + int signal) +{ + int status = -1; + + status = kill (pid, signal); + + if (status < 0) { + if (errno == ESRCH) { + g_debug ("Child process %lu was already dead.", + (unsigned long) pid); + } else { + char buf [1024]; + snprintf (buf, + sizeof (buf), + "Couldn't kill child process %lu", + (unsigned long) pid); + perror (buf); + } + } + + return status; +} + +static gboolean +ck_history_watchdog (GdmUserManager *manager) +{ + if (manager->priv->ck_history_pid > 0) { + g_debug ("Killing ck-history process"); + signal_pid (manager->priv->ck_history_pid, SIGTERM); + manager->priv->ck_history_pid = 0; + } + + manager->priv->ck_history_watchdog_id = 0; + return FALSE; +} + +static gboolean +load_ck_history (GdmUserManager *manager) { char *command; + char *since; const char *seat_id; GError *error; gboolean res; char **argv; int standard_out; GIOChannel *channel; + GTimeVal tv; + + g_assert (manager->priv->ck_history_id == 0); + + command = NULL; seat_id = NULL; - if (manager->priv->seat_id != NULL - && g_str_has_prefix (manager->priv->seat_id, "/org/freedesktop/ConsoleKit/")) { + if (manager->priv->seat.id != NULL + && g_str_has_prefix (manager->priv->seat.id, "/org/freedesktop/ConsoleKit/")) { - seat_id = manager->priv->seat_id + strlen ("/org/freedesktop/ConsoleKit/"); + seat_id = manager->priv->seat.id + strlen ("/org/freedesktop/ConsoleKit/"); } if (seat_id == NULL) { - g_debug ("Unable to find users: no seat-id found"); - return; + g_warning ("Unable to load CK history: no seat-id found"); + goto out; } - command = g_strdup_printf ("ck-history --frequent --seat='%s' --session-type=''", + g_get_current_time (&tv); + tv.tv_sec -= LOGIN_FREQUENCY_TIME_WINDOW_SECS; + since = g_time_val_to_iso8601 (&tv); + + command = g_strdup_printf ("ck-history --frequent --since='%s' --seat='%s' --session-type=''", + since, seat_id); + g_free (since); g_debug ("GdmUserManager: running '%s'", command); error = NULL; if (! g_shell_parse_argv (command, NULL, &argv, &error)) { - g_warning ("Could not parse command: %s", error->message); - g_error_free (error); + if (error != NULL) { + g_warning ("Could not parse command: %s", error->message); + g_error_free (error); + } else { + g_warning ("Could not parse command"); + } goto out; } @@ -1257,15 +2116,19 @@ reload_ck_history (GdmUserManager *manager) G_SPAWN_SEARCH_PATH, NULL, NULL, - NULL, /* pid */ + &manager->priv->ck_history_pid, /* pid */ NULL, &standard_out, NULL, &error); g_strfreev (argv); if (! res) { - g_warning ("Unable to run ck-history: %s", error->message); - g_error_free (error); + if (error != NULL) { + g_warning ("Unable to run ck-history: %s", error->message); + g_error_free (error); + } else { + g_warning ("Unable to run ck-history"); + } goto out; } @@ -1274,6 +2137,7 @@ reload_ck_history (GdmUserManager *manager) g_io_channel_set_flags (channel, g_io_channel_get_flags (channel) | G_IO_FLAG_NONBLOCK, NULL); + manager->priv->ck_history_watchdog_id = g_timeout_add_seconds (1, (GSourceFunc) ck_history_watchdog, manager); manager->priv->ck_history_id = g_io_add_watch (channel, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, (GIOFunc)ck_history_watch, @@ -1281,20 +2145,31 @@ reload_ck_history (GdmUserManager *manager) g_io_channel_unref (channel); out: + g_free (command); + + return manager->priv->ck_history_id != 0; } static void -reload_passwd (GdmUserManager *manager) +reload_passwd_file (GHashTable *valid_shells, + GSList *exclude_users, + GSList *include_users, + gboolean include_all, + GHashTable *current_users_by_name, + GSList **added_users, + GSList **removed_users) { - struct passwd *pwent; - GSList *old_users; - GSList *new_users; - GSList *list; - FILE *fp; + FILE *fp; + GHashTableIter iter; + GHashTable *new_users_by_name; + GdmUser *user; + char *name; - old_users = NULL; - new_users = NULL; + new_users_by_name = g_hash_table_new_full (g_str_hash, + g_str_equal, + NULL, + g_object_unref); errno = 0; fp = fopen (PATH_PASSWD, "r"); @@ -1303,74 +2178,127 @@ reload_passwd (GdmUserManager *manager) goto out; } - g_hash_table_foreach (manager->priv->users, listify_hash_values_hfunc, &old_users); - g_slist_foreach (old_users, (GFunc) g_object_ref, NULL); - /* Make sure we keep users who are logged in no matter what. */ - for (list = old_users; list; list = list->next) { - if (gdm_user_get_num_sessions (list->data) > 0) { - g_object_freeze_notify (G_OBJECT (list->data)); - new_users = g_slist_prepend (new_users, g_object_ref (list->data)); + g_hash_table_iter_init (&iter, current_users_by_name); + while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer *) &user)) { + struct passwd *pwent; + + get_pwent_for_name (name, &pwent); + if (pwent == NULL) { + continue; + } + + g_object_freeze_notify (G_OBJECT (user)); + _gdm_user_update_from_pwent (user, pwent); + g_hash_table_insert (new_users_by_name, (char *)gdm_user_get_user_name (user), g_object_ref (user)); + } + + if (include_users != NULL) { + GSList *l; + for (l = include_users; l != NULL; l = l->next) { + struct passwd *pwent; + + get_pwent_for_name (l->data, &pwent); + if (pwent == NULL) { + continue; + } + + user = g_hash_table_lookup (new_users_by_name, pwent->pw_name); + if (user != NULL) { + /* already there */ + continue; + } + + user = g_hash_table_lookup (current_users_by_name, pwent->pw_name); + if (user == NULL) { + user = g_object_new (GDM_TYPE_USER, NULL); + } else { + g_object_ref (user); + } + g_object_freeze_notify (G_OBJECT (user)); + _gdm_user_update_from_pwent (user, pwent); + g_hash_table_insert (new_users_by_name, (char *)gdm_user_get_user_name (user), user); } } - for (pwent = fgetpwent (fp); pwent != NULL; pwent = fgetpwent (fp)) { - GdmUser *user; + if (include_all != TRUE) { + g_debug ("GdmUserManager: include_all is FALSE"); + } else { + struct passwd *pwent; - user = NULL; + g_debug ("GdmUserManager: include_all is TRUE"); - /* Skip users below MinimalUID... */ - if (pwent->pw_uid < DEFAULT_MINIMAL_UID) { - continue; + for (pwent = fgetpwent (fp); + pwent != NULL; + pwent = fgetpwent (fp)) { + + /* Skip users below MinimalUID... */ + if (pwent->pw_uid < FALLBACK_MINIMAL_UID) { + continue; + } + + /* ...And users w/ invalid shells... */ + if (pwent->pw_shell == NULL + || !g_hash_table_lookup (valid_shells, pwent->pw_shell)) { + g_debug ("GdmUserManager: skipping user with bad shell: %s", pwent->pw_name); + continue; + } + + /* always exclude the "gdm" user. */ + if (strcmp (pwent->pw_name, GDM_USERNAME) == 0) { + continue; + } + + /* ...And explicitly excluded users */ + if (exclude_users != NULL) { + GSList *found; + + found = g_slist_find_custom (exclude_users, + pwent->pw_name, + match_name_cmpfunc); + if (found != NULL) { + g_debug ("GdmUserManager: explicitly skipping user: %s", pwent->pw_name); + continue; + } + } + + user = g_hash_table_lookup (new_users_by_name, pwent->pw_name); + if (user != NULL) { + /* already there */ + continue; + } + + user = g_hash_table_lookup (current_users_by_name, pwent->pw_name); + if (user == NULL) { + user = g_object_new (GDM_TYPE_USER, NULL); + } else { + g_object_ref (user); + } + + /* Freeze & update users not already in the new list */ + g_object_freeze_notify (G_OBJECT (user)); + _gdm_user_update_from_pwent (user, pwent); + g_hash_table_insert (new_users_by_name, (char *)gdm_user_get_user_name (user), user); } - - /* ...And users w/ invalid shells... */ - if (pwent->pw_shell == NULL || - !g_hash_table_lookup (manager->priv->shells, pwent->pw_shell)) { - g_debug ("GdmUserManager: skipping user with bad shell: %s", pwent->pw_name); - continue; - } - - /* ...And explicitly excluded users */ - if (g_hash_table_lookup (manager->priv->exclusions, pwent->pw_name)) { - g_debug ("GdmUserManager: explicitly skipping user: %s", pwent->pw_name); - continue; - } - - user = g_hash_table_lookup (manager->priv->users, pwent->pw_name); - - /* Update users already in the *new* list */ - if (g_slist_find (new_users, user)) { - _gdm_user_update (user, pwent); - continue; - } - - if (user == NULL) { - user = create_user (manager); - } else { - g_object_ref (user); - } - - /* Freeze & update users not already in the new list */ - g_object_freeze_notify (G_OBJECT (user)); - _gdm_user_update (user, pwent); - - new_users = g_slist_prepend (new_users, user); } /* Go through and handle added users */ - for (list = new_users; list; list = list->next) { - if (! g_slist_find (old_users, list->data)) { - add_user (manager, list->data); + g_hash_table_iter_init (&iter, new_users_by_name); + while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer *) &user)) { + GdmUser *user2; + user2 = g_hash_table_lookup (current_users_by_name, name); + if (user2 == NULL) { + *added_users = g_slist_prepend (*added_users, g_object_ref (user)); } } /* Go through and handle removed users */ - for (list = old_users; list; list = list->next) { - if (! g_slist_find (new_users, list->data)) { - g_signal_emit (manager, signals[USER_REMOVED], 0, list->data); - g_hash_table_remove (manager->priv->users, - gdm_user_get_user_name (list->data)); + g_hash_table_iter_init (&iter, current_users_by_name); + while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer *) &user)) { + GdmUser *user2; + user2 = g_hash_table_lookup (new_users_by_name, name); + if (user2 == NULL) { + *removed_users = g_slist_prepend (*removed_users, g_object_ref (user)); } } @@ -1379,39 +2307,222 @@ reload_passwd (GdmUserManager *manager) fclose (fp); - g_slist_foreach (new_users, (GFunc) g_object_thaw_notify, NULL); - g_slist_foreach (new_users, (GFunc) g_object_unref, NULL); - g_slist_free (new_users); + g_hash_table_iter_init (&iter, new_users_by_name); + while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer *) &user)) { + g_object_thaw_notify (G_OBJECT (user)); + } - g_slist_foreach (old_users, (GFunc) g_object_unref, NULL); - g_slist_free (old_users); + g_hash_table_destroy (new_users_by_name); } +typedef struct { + GdmUserManager *manager; + GSList *exclude_users; + GSList *include_users; + gboolean include_all; + GHashTable *shells; + GHashTable *current_users_by_name; + GSList *added_users; + GSList *removed_users; +} PasswdData; + static void -reload_users (GdmUserManager *manager) +passwd_data_free (PasswdData *data) { - reload_ck_history (manager); - reload_passwd (manager); + if (data->manager != NULL) { + g_object_unref (data->manager); + } + + g_slist_foreach (data->added_users, (GFunc) g_object_unref, NULL); + g_slist_free (data->added_users); + + g_slist_foreach (data->removed_users, (GFunc) g_object_unref, NULL); + g_slist_free (data->removed_users); + + g_slist_foreach (data->exclude_users, (GFunc) g_free, NULL); + g_slist_free (data->exclude_users); + + g_slist_foreach (data->include_users, (GFunc) g_free, NULL); + g_slist_free (data->include_users); + + g_slice_free (PasswdData, data); } static gboolean -reload_users_timeout (GdmUserManager *manager) +reload_passwd_job_done (PasswdData *data) { - reload_users (manager); - manager->priv->reload_id = 0; + GSList *l; + + g_debug ("GdmUserManager: done reloading passwd file"); + + /* Go through and handle added users */ + for (l = data->added_users; l != NULL; l = l->next) { + add_user (data->manager, l->data); + } + + /* Go through and handle removed users */ + for (l = data->removed_users; l != NULL; l = l->next) { + remove_user (data->manager, l->data); + } + + data->manager->priv->load_passwd_pending = FALSE; + + if (! data->manager->priv->is_loaded) { + maybe_set_is_loaded (data->manager); + + if (data->manager->priv->include_all == TRUE) { + monitor_local_users (data->manager); + } + } + + passwd_data_free (data); return FALSE; } -static void -queue_reload_users (GdmUserManager *manager) +static gboolean +do_reload_passwd_job (GIOSchedulerJob *job, + GCancellable *cancellable, + PasswdData *data) { - if (manager->priv->reload_id > 0) { + g_debug ("GdmUserManager: reloading passwd file worker"); + + reload_passwd_file (data->shells, + data->exclude_users, + data->include_users, + data->include_all, + data->current_users_by_name, + &data->added_users, + &data->removed_users); + + g_io_scheduler_job_send_to_mainloop_async (job, + (GSourceFunc) reload_passwd_job_done, + data, + NULL); + + return FALSE; +} + +static GSList * +slist_deep_copy (const GSList *list) +{ + GSList *retval; + GSList *l; + + if (list == NULL) + return NULL; + + retval = g_slist_copy ((GSList *) list); + for (l = retval; l != NULL; l = l->next) { + l->data = g_strdup (l->data); + } + + return retval; +} + +static void +schedule_reload_passwd (GdmUserManager *manager) +{ + PasswdData *passwd_data; + + manager->priv->load_passwd_pending = TRUE; + + passwd_data = g_slice_new0 (PasswdData); + passwd_data->manager = g_object_ref (manager); + passwd_data->shells = manager->priv->shells; + passwd_data->exclude_users = slist_deep_copy (manager->priv->exclude_usernames); + passwd_data->include_users = slist_deep_copy (manager->priv->include_usernames); + passwd_data->include_all = manager->priv->include_all; + passwd_data->current_users_by_name = manager->priv->users_by_name; + passwd_data->added_users = NULL; + passwd_data->removed_users = NULL; + + g_debug ("GdmUserManager: scheduling a passwd file update"); + + g_io_scheduler_push_job ((GIOSchedulerJobFunc) do_reload_passwd_job, + passwd_data, + NULL, + G_PRIORITY_DEFAULT, + NULL); +} + +static void +load_sessions_from_array (GdmUserManager *manager, + const char * const *session_ids, + int number_of_sessions) +{ + int i; + + for (i = 0; i < number_of_sessions; i++) { + load_new_session (manager, session_ids[i]); + } +} + +static void +on_get_sessions_finished (DBusGProxy *proxy, + DBusGProxyCall *call, + GdmUserManager *manager) +{ + GError *error; + gboolean res; + GPtrArray *sessions; + + g_assert (manager->priv->get_sessions_call == call); + + error = NULL; + sessions = NULL; + res = dbus_g_proxy_end_call (proxy, + call, + &error, + GDM_DBUS_TYPE_G_OBJECT_PATH_ARRAY, + &sessions, + G_TYPE_INVALID); + + if (! res) { + if (error != NULL) { + g_warning ("unable to determine sessions for seat: %s", + error->message); + g_error_free (error); + } else { + g_warning ("unable to determine sessions for seat"); + } return; } - g_signal_emit (G_OBJECT (manager), signals[LOADING_USERS], 0); - manager->priv->reload_id = g_idle_add ((GSourceFunc)reload_users_timeout, manager); + manager->priv->get_sessions_call = NULL; + g_assert (sessions->len <= G_MAXINT); + load_sessions_from_array (manager, + (const char * const *) sessions->pdata, + (int) sessions->len); + g_ptr_array_foreach (sessions, (GFunc) g_free, NULL); + g_ptr_array_free (sessions, TRUE); + maybe_set_is_loaded (manager); +} + +static void +load_sessions (GdmUserManager *manager) +{ + DBusGProxyCall *call; + + if (manager->priv->seat.proxy == NULL) { + g_debug ("GdmUserManager: no seat proxy; can't load sessions"); + return; + } + + call = dbus_g_proxy_begin_call (manager->priv->seat.proxy, + "GetSessions", + (DBusGProxyCallNotify) + on_get_sessions_finished, + manager, + NULL, + G_TYPE_INVALID); + + if (call == NULL) { + g_warning ("GdmUserManager: failed to make GetSessions call"); + return; + } + + manager->priv->get_sessions_call = call; } static void @@ -1438,6 +2549,109 @@ reload_shells (GdmUserManager *manager) endusershell (); } +static void +load_users_manually (GdmUserManager *manager) +{ + gboolean res; + + manager->priv->shells = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + NULL); + reload_shells (manager); + + load_sessions (manager); + + res = load_ck_history (manager); + schedule_reload_passwd (manager); +} + +static void +load_users (GdmUserManager *manager) +{ + g_assert (manager->priv->accounts_proxy != NULL); + g_debug ("GdmUserManager: calling 'ListCachedUsers'"); + + dbus_g_proxy_begin_call (manager->priv->accounts_proxy, + "ListCachedUsers", + on_list_cached_users_finished, + manager, + NULL, + G_TYPE_INVALID); + manager->priv->listing_cached_users = TRUE; +} + +static void +load_seat_incrementally (GdmUserManager *manager) +{ + g_assert (manager->priv->seat.proxy == NULL); + + switch (manager->priv->seat.state) { + case GDM_USER_MANAGER_SEAT_STATE_GET_SESSION_ID: + get_current_session_id (manager); + break; + case GDM_USER_MANAGER_SEAT_STATE_GET_ID: + get_seat_id_for_current_session (manager); + break; + case GDM_USER_MANAGER_SEAT_STATE_GET_PROXY: + get_seat_proxy (manager); + break; + case GDM_USER_MANAGER_SEAT_STATE_LOADED: + break; + default: + g_assert_not_reached (); + } + + if (manager->priv->seat.state == GDM_USER_MANAGER_SEAT_STATE_LOADED) { + gboolean res; + + load_sessions (manager); + res = load_ck_history (manager); + } + + maybe_set_is_loaded (manager); +} + +static gboolean +load_idle (GdmUserManager *manager) +{ + manager->priv->seat.state = GDM_USER_MANAGER_SEAT_STATE_UNLOADED + 1; + load_seat_incrementally (manager); + load_users (manager); + manager->priv->load_id = 0; + + return FALSE; +} + +static void +queue_load_seat_and_users (GdmUserManager *manager) +{ + if (manager->priv->load_id > 0) { + return; + } + + manager->priv->load_id = g_idle_add ((GSourceFunc)load_idle, manager); +} + +static gboolean +reload_passwd_idle (GdmUserManager *manager) +{ + schedule_reload_passwd (manager); + manager->priv->reload_passwd_id = 0; + + return FALSE; +} + +static void +queue_reload_passwd (GdmUserManager *manager) +{ + if (manager->priv->reload_passwd_id > 0) { + g_source_remove (manager->priv->reload_passwd_id); + } + + manager->priv->reload_passwd_id = g_timeout_add_seconds (RELOAD_PASSWD_THROTTLE_SECS, (GSourceFunc)reload_passwd_idle, manager); +} + static void on_shells_monitor_changed (GFileMonitor *monitor, GFile *file, @@ -1451,7 +2665,7 @@ on_shells_monitor_changed (GFileMonitor *monitor, } reload_shells (manager); - reload_passwd (manager); + queue_reload_passwd (manager); } static void @@ -1466,54 +2680,186 @@ on_passwd_monitor_changed (GFileMonitor *monitor, return; } - reload_passwd (manager); + queue_reload_passwd (manager); } static void -ignore_log_handler (const char *log_domain, - GLogLevelFlags log_level, - const char *message, - gpointer user_data) +gdm_user_manager_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) { - return; -} + GdmUserManager *manager; -static void -check_gdm_debug (void) -{ - GDebugKey gdmkeys[] = { { "gdm", 1 } }; - const char *gnome_shell_debug = g_getenv ("GNOME_SHELL_DEBUG"); + manager = GDM_USER_MANAGER (object); - if (!gnome_shell_debug || - !g_parse_debug_string (gnome_shell_debug, gdmkeys, 1)) { - g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, - ignore_log_handler, NULL); + switch (prop_id) { + case PROP_IS_LOADED: + g_value_set_boolean (value, manager->priv->is_loaded); + break; + case PROP_HAS_MULTIPLE_USERS: + g_value_set_boolean (value, manager->priv->has_multiple_users); + break; + case PROP_INCLUDE_ALL: + g_value_set_boolean (value, manager->priv->include_all); + break; + case PROP_INCLUDE_USERNAMES_LIST: + g_value_set_pointer (value, manager->priv->include_usernames); + break; + case PROP_EXCLUDE_USERNAMES_LIST: + g_value_set_pointer (value, manager->priv->exclude_usernames); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; } } +static void +set_include_usernames (GdmUserManager *manager, + GSList *list) +{ + if (manager->priv->include_usernames != NULL) { + g_slist_foreach (manager->priv->include_usernames, (GFunc) g_free, NULL); + g_slist_free (manager->priv->include_usernames); + } + manager->priv->include_usernames = slist_deep_copy (list); +} + +static void +set_exclude_usernames (GdmUserManager *manager, + GSList *list) +{ + if (manager->priv->exclude_usernames != NULL) { + g_slist_foreach (manager->priv->exclude_usernames, (GFunc) g_free, NULL); + g_slist_free (manager->priv->exclude_usernames); + } + manager->priv->exclude_usernames = slist_deep_copy (list); +} + +static void +set_include_all (GdmUserManager *manager, + gboolean all) +{ + if (manager->priv->include_all != all) { + manager->priv->include_all = all; + } +} + +static void +gdm_user_manager_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GdmUserManager *self; + + self = GDM_USER_MANAGER (object); + + switch (prop_id) { + case PROP_INCLUDE_ALL: + set_include_all (self, g_value_get_boolean (value)); + break; + case PROP_INCLUDE_USERNAMES_LIST: + set_include_usernames (self, g_value_get_pointer (value)); + break; + case PROP_EXCLUDE_USERNAMES_LIST: + set_exclude_usernames (self, g_value_get_pointer (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +monitor_local_users (GdmUserManager *manager) +{ + GFile *file; + GError *error; + + g_debug ("GdmUserManager: Monitoring local users"); + + /* /etc/shells */ + file = g_file_new_for_path (_PATH_SHELLS); + error = NULL; + manager->priv->shells_monitor = g_file_monitor_file (file, + G_FILE_MONITOR_NONE, + NULL, + &error); + if (manager->priv->shells_monitor != NULL) { + g_signal_connect (manager->priv->shells_monitor, + "changed", + G_CALLBACK (on_shells_monitor_changed), + manager); + } else { + g_warning ("Unable to monitor %s: %s", _PATH_SHELLS, error->message); + g_error_free (error); + } + g_object_unref (file); + + /* /etc/passwd */ + file = g_file_new_for_path (PATH_PASSWD); + manager->priv->passwd_monitor = g_file_monitor_file (file, + G_FILE_MONITOR_NONE, + NULL, + &error); + if (manager->priv->passwd_monitor != NULL) { + g_signal_connect (manager->priv->passwd_monitor, + "changed", + G_CALLBACK (on_passwd_monitor_changed), + manager); + } else { + g_warning ("Unable to monitor %s: %s", PATH_PASSWD, error->message); + g_error_free (error); + } + g_object_unref (file); +} + static void gdm_user_manager_class_init (GdmUserManagerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = gdm_user_manager_finalize; + object_class->get_property = gdm_user_manager_get_property; + object_class->set_property = gdm_user_manager_set_property; + + g_object_class_install_property (object_class, + PROP_IS_LOADED, + g_param_spec_boolean ("is-loaded", + NULL, + NULL, + FALSE, + G_PARAM_READABLE)); + g_object_class_install_property (object_class, + PROP_HAS_MULTIPLE_USERS, + g_param_spec_boolean ("has-multiple-users", + NULL, + NULL, + FALSE, + G_PARAM_READABLE)); + g_object_class_install_property (object_class, + PROP_INCLUDE_ALL, + g_param_spec_boolean ("include-all", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_INCLUDE_USERNAMES_LIST, + g_param_spec_pointer ("include-usernames-list", + NULL, + NULL, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_EXCLUDE_USERNAMES_LIST, + g_param_spec_pointer ("exclude-usernames-list", + NULL, + NULL, + G_PARAM_READWRITE)); - signals [LOADING_USERS] = - g_signal_new ("loading-users", - G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (GdmUserManagerClass, loading_users), - NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); - signals [USERS_LOADED] = - g_signal_new ("users-loaded", - G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (GdmUserManagerClass, users_loaded), - NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); signals [USER_ADDED] = g_signal_new ("user-added", G_TYPE_FROM_CLASS (klass), @@ -1538,26 +2884,40 @@ gdm_user_manager_class_init (GdmUserManagerClass *klass) NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GDM_TYPE_USER); - signals [USER_LOGIN_FREQUENCY_CHANGED] = - g_signal_new ("user-login-frequency-changed", + signals [USER_CHANGED] = + g_signal_new ("user-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (GdmUserManagerClass, user_login_frequency_changed), + G_STRUCT_OFFSET (GdmUserManagerClass, user_changed), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GDM_TYPE_USER); g_type_class_add_private (klass, sizeof (GdmUserManagerPrivate)); - check_gdm_debug (); +} + +/** + * gdm_user_manager_queue_load: + * @manager: a #GdmUserManager + * + * Queue loading users into user manager. This must be called, and the + * #GdmUserManager:is-loaded property must be %TRUE before calling + * gdm_user_manager_list_users() + */ +void +gdm_user_manager_queue_load (GdmUserManager *manager) +{ + g_return_if_fail (GDM_IS_USER_MANAGER (manager)); + + if (! manager->priv->is_loaded) { + queue_load_seat_and_users (manager); + } } static void gdm_user_manager_init (GdmUserManager *manager) { - int i; - GFile *file; GError *error; - const char *exclude_default[] = DEFAULT_EXCLUDE; manager->priv = GDM_USER_MANAGER_GET_PRIVATE (manager); @@ -1567,73 +2927,41 @@ gdm_user_manager_init (GdmUserManager *manager) g_free, g_free); - /* exclusions */ - manager->priv->exclusions = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - NULL); - for (i = 0; exclude_default[i] != NULL; i++) { - g_hash_table_insert (manager->priv->exclusions, - g_strdup (exclude_default [i]), - GUINT_TO_POINTER (TRUE)); - } + /* users */ + manager->priv->users_by_name = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + g_object_unref); + + manager->priv->users_by_object_path = g_hash_table_new_full (g_str_hash, + g_str_equal, + NULL, + g_object_unref); + + g_assert (manager->priv->seat.proxy == NULL); - /* /etc/shells */ - manager->priv->shells = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - NULL); - reload_shells (manager); - file = g_file_new_for_path (_PATH_SHELLS); error = NULL; - manager->priv->shells_monitor = g_file_monitor_file (file, - G_FILE_MONITOR_NONE, - NULL, - &error); - if (manager->priv->shells_monitor != NULL) { - g_signal_connect (manager->priv->shells_monitor, - "changed", - G_CALLBACK (on_shells_monitor_changed), - manager); - } else { - g_warning ("Unable to monitor %s: %s", _PATH_SHELLS, error->message); - g_error_free (error); + manager->priv->connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error); + if (manager->priv->connection == NULL) { + if (error != NULL) { + g_warning ("Failed to connect to the D-Bus daemon: %s", error->message); + g_error_free (error); + } else { + g_warning ("Failed to connect to the D-Bus daemon"); + } + return; } - g_object_unref (file); - /* /etc/passwd */ - manager->priv->users = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - (GDestroyNotify) g_object_run_dispose); - file = g_file_new_for_path (PATH_PASSWD); - manager->priv->passwd_monitor = g_file_monitor_file (file, - G_FILE_MONITOR_NONE, - NULL, - &error); - if (manager->priv->passwd_monitor != NULL) { - g_signal_connect (manager->priv->passwd_monitor, - "changed", - G_CALLBACK (on_passwd_monitor_changed), - manager); - } else { - g_warning ("Unable to monitor %s: %s", PATH_PASSWD, error->message); - g_error_free (error); - } - g_object_unref (file); + get_accounts_proxy (manager); - - get_seat_proxy (manager); - - queue_reload_users (manager); - - manager->priv->users_dirty = FALSE; + manager->priv->seat.state = GDM_USER_MANAGER_SEAT_STATE_UNLOADED; } static void gdm_user_manager_finalize (GObject *object) { GdmUserManager *manager; + GSList *node; g_return_if_fail (object != NULL); g_return_if_fail (GDM_IS_USER_MANAGER (object)); @@ -1642,8 +2970,51 @@ gdm_user_manager_finalize (GObject *object) g_return_if_fail (manager->priv != NULL); - if (manager->priv->seat_proxy != NULL) { - g_object_unref (manager->priv->seat_proxy); + if (manager->priv->ck_history_pid > 0) { + g_debug ("Killing ck-history process"); + signal_pid (manager->priv->ck_history_pid, SIGTERM); + } + + g_slist_foreach (manager->priv->new_sessions, + (GFunc) unload_new_session, NULL); + g_slist_free (manager->priv->new_sessions); + + g_slist_foreach (manager->priv->fetch_user_requests, + (GFunc) free_fetch_user_request, NULL); + g_slist_free (manager->priv->fetch_user_requests); + + node = manager->priv->new_users; + while (node != NULL) { + GdmUser *user; + GSList *next_node; + + user = GDM_USER (node->data); + next_node = node->next; + + g_signal_handlers_disconnect_by_func (user, on_new_user_loaded, manager); + g_object_unref (user); + manager->priv->new_users = g_slist_delete_link (manager->priv->new_users, node); + node = next_node; + } + + unload_seat (manager); + + if (manager->priv->exclude_usernames != NULL) { + g_slist_foreach (manager->priv->exclude_usernames, (GFunc) g_free, NULL); + g_slist_free (manager->priv->exclude_usernames); + } + + if (manager->priv->include_usernames != NULL) { + g_slist_foreach (manager->priv->include_usernames, (GFunc) g_free, NULL); + g_slist_free (manager->priv->include_usernames); + } + + if (manager->priv->seat.proxy != NULL) { + g_object_unref (manager->priv->seat.proxy); + } + + if (manager->priv->accounts_proxy != NULL) { + g_object_unref (manager->priv->accounts_proxy); } if (manager->priv->ck_history_id != 0) { @@ -1651,28 +3022,58 @@ gdm_user_manager_finalize (GObject *object) manager->priv->ck_history_id = 0; } - if (manager->priv->reload_id > 0) { - g_source_remove (manager->priv->reload_id); - manager->priv->reload_id = 0; + if (manager->priv->ck_history_watchdog_id != 0) { + g_source_remove (manager->priv->ck_history_watchdog_id); + manager->priv->ck_history_watchdog_id = 0; + } + + if (manager->priv->load_id > 0) { + g_source_remove (manager->priv->load_id); + manager->priv->load_id = 0; + } + + if (manager->priv->reload_passwd_id > 0) { + g_source_remove (manager->priv->reload_passwd_id); + manager->priv->reload_passwd_id = 0; } g_hash_table_destroy (manager->priv->sessions); - g_file_monitor_cancel (manager->priv->passwd_monitor); - g_hash_table_destroy (manager->priv->users); + if (manager->priv->passwd_monitor != NULL) { + g_file_monitor_cancel (manager->priv->passwd_monitor); + } - g_file_monitor_cancel (manager->priv->shells_monitor); - g_hash_table_destroy (manager->priv->shells); + g_hash_table_destroy (manager->priv->users_by_name); + g_hash_table_destroy (manager->priv->users_by_object_path); - g_free (manager->priv->seat_id); + if (manager->priv->shells_monitor != NULL) { + g_file_monitor_cancel (manager->priv->shells_monitor); + } + + if (manager->priv->shells != NULL) { + g_hash_table_destroy (manager->priv->shells); + } G_OBJECT_CLASS (gdm_user_manager_parent_class)->finalize (object); } +static void +gdm_user_muted_debug_log_handler (const char *log_domain, + GLogLevelFlags log_level, + const char *message, + gpointer data) +{ + /* Intentionally empty to discard message */ +} + /** * gdm_user_manager_ref_default: * - * Returns: (transfer full): The singleton #GdmUserManager object. + * Queue loading users into user manager. This must be called, and the + * #GdmUserManager:is-loaded property must be %TRUE before calling + * gdm_user_manager_list_users() + * + * Returns: (transfer full): user manager object */ GdmUserManager * gdm_user_manager_ref_default (void) @@ -1683,6 +3084,11 @@ gdm_user_manager_ref_default (void) user_manager_object = g_object_new (GDM_TYPE_USER_MANAGER, NULL); g_object_add_weak_pointer (user_manager_object, (gpointer *) &user_manager_object); + + /* We don't normally care about user manager messages in the shell, + * so mute them */ + g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, + gdm_user_muted_debug_log_handler, NULL); } return GDM_USER_MANAGER (user_manager_object); diff --git a/src/gdmuser/gdm-user-manager.h b/src/gdmuser/gdm-user-manager.h index f57b4cd1f..8dd9edec3 100644 --- a/src/gdmuser/gdm-user-manager.h +++ b/src/gdmuser/gdm-user-manager.h @@ -18,8 +18,8 @@ * */ -#ifndef __GDM_USER_MANAGER_H -#define __GDM_USER_MANAGER_H +#ifndef __GDM_USER_MANAGER_H__ +#define __GDM_USER_MANAGER_H__ #include @@ -35,34 +35,35 @@ G_BEGIN_DECLS #define GDM_USER_MANAGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_USER_MANAGER, GdmUserManagerClass)) typedef struct GdmUserManagerPrivate GdmUserManagerPrivate; +typedef struct GdmUserManager GdmUserManager; +typedef struct GdmUserManagerClass GdmUserManagerClass; +typedef enum GdmUserManagerError GdmUserManagerError; -typedef struct +struct GdmUserManager { GObject parent; GdmUserManagerPrivate *priv; -} GdmUserManager; +}; -typedef struct +struct GdmUserManagerClass { GObjectClass parent_class; - void (* loading_users) (GdmUserManager *user_manager); - void (* users_loaded) (GdmUserManager *user_manager); void (* user_added) (GdmUserManager *user_manager, GdmUser *user); void (* user_removed) (GdmUserManager *user_manager, GdmUser *user); void (* user_is_logged_in_changed) (GdmUserManager *user_manager, GdmUser *user); - void (* user_login_frequency_changed) (GdmUserManager *user_manager, - GdmUser *user); -} GdmUserManagerClass; + void (* user_changed) (GdmUserManager *user_manager, + GdmUser *user); +}; -typedef enum +enum GdmUserManagerError { GDM_USER_MANAGER_ERROR_GENERAL, GDM_USER_MANAGER_ERROR_KEY_NOT_FOUND -} GdmUserManagerError; +}; #define GDM_USER_MANAGER_ERROR gdm_user_manager_error_quark () @@ -71,6 +72,7 @@ GType gdm_user_manager_get_type (void); GdmUserManager * gdm_user_manager_ref_default (void); +void gdm_user_manager_queue_load (GdmUserManager *manager); GSList * gdm_user_manager_list_users (GdmUserManager *manager); GdmUser * gdm_user_manager_get_user (GdmUserManager *manager, const char *username); @@ -80,6 +82,8 @@ GdmUser * gdm_user_manager_get_user_by_uid (GdmUserManager *mana gboolean gdm_user_manager_activate_user_session (GdmUserManager *manager, GdmUser *user); +gboolean gdm_user_manager_can_switch (GdmUserManager *manager); + gboolean gdm_user_manager_goto_login_session (GdmUserManager *manager); G_END_DECLS diff --git a/src/gdmuser/gdm-user-private.h b/src/gdmuser/gdm-user-private.h index d716f3ef5..05bda556d 100644 --- a/src/gdmuser/gdm-user-private.h +++ b/src/gdmuser/gdm-user-private.h @@ -21,8 +21,8 @@ * Private interfaces to the GdmUser object */ -#ifndef __GDM_USER_PRIVATE_H -#define __GDM_USER_PRIVATE_H +#ifndef __GDM_USER_PRIVATE_H_ +#define __GDM_USER_PRIVATE_H_ #include @@ -30,15 +30,20 @@ G_BEGIN_DECLS -void _gdm_user_update (GdmUser *user, - const struct passwd *pwent); +void _gdm_user_update_from_object_path (GdmUser *user, + const char *object_path); + +void _gdm_user_update_from_pwent (GdmUser *user, + const struct passwd *pwent); + +void _gdm_user_update_login_frequency (GdmUser *user, + guint64 login_frequency); + void _gdm_user_add_session (GdmUser *user, const char *session_id); void _gdm_user_remove_session (GdmUser *user, const char *session_id); -void _gdm_user_icon_changed (GdmUser *user); - G_END_DECLS -#endif /* !__GDM_USER_PRIVATE_H */ +#endif /* !__GDM_USER_PRIVATE__ */ diff --git a/src/gdmuser/gdm-user.c b/src/gdmuser/gdm-user.c index bd73b9559..710510a44 100644 --- a/src/gdmuser/gdm-user.c +++ b/src/gdmuser/gdm-user.c @@ -26,12 +26,13 @@ #include #include -/* Note on sync with gdm; need to use -lib here */ -#include +#include + +#include +#include #include #include -#include "gdm-user-manager.h" #include "gdm-user-private.h" #define GDM_USER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDM_TYPE_USER, GdmUserClass)) @@ -39,25 +40,18 @@ #define GDM_USER_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS ((object), GDM_TYPE_USER, GdmUserClass)) #define GLOBAL_FACEDIR DATADIR "/faces" -#define MAX_ICON_SIZE 128 #define MAX_FILE_SIZE 65536 -#define MINIMAL_UID 100 -#define RELAX_GROUP TRUE -#define RELAX_OTHER TRUE + +#define ACCOUNTS_NAME "org.freedesktop.Accounts" +#define ACCOUNTS_USER_INTERFACE "org.freedesktop.Accounts.User" enum { PROP_0, - PROP_MANAGER, - PROP_REAL_NAME, - PROP_USER_NAME, - PROP_UID, - PROP_HOME_DIR, - PROP_SHELL, - PROP_LOGIN_FREQUENCY, + PROP_IS_LOADED }; enum { - ICON_CHANGED, + CHANGED, SESSIONS_CHANGED, LAST_SIGNAL }; @@ -65,28 +59,29 @@ enum { struct _GdmUser { GObject parent; - GdmUserManager *manager; + DBusGConnection *connection; + DBusGProxy *accounts_proxy; + DBusGProxy *object_proxy; + DBusGProxyCall *get_all_call; + char *object_path; uid_t uid; char *user_name; char *real_name; - char *home_dir; - char *shell; + char *icon_file; GList *sessions; gulong login_frequency; - GFileMonitor *icon_monitor; + guint is_loaded : 1; }; struct _GdmUserClass { GObjectClass parent_class; - - void (* icon_changed) (GdmUser *user); - void (* sessions_changed) (GdmUser *user); }; static void gdm_user_finalize (GObject *object); +static gboolean check_user_file (const char *filename); static guint signals[LAST_SIGNAL] = { 0 }; @@ -150,84 +145,39 @@ gdm_user_get_num_sessions (GdmUser *user) return g_list_length (user->sessions); } -/** - * gdm_user_get_sessions: - * @user: a #GdmUser - * - * Returns: (transfer none) (element-type utf8): Session identifier strings - */ -GList * -gdm_user_get_sessions (GdmUser *user) -{ - return user->sessions; -} - static void -_gdm_user_set_login_frequency (GdmUser *user, - gulong login_frequency) -{ - user->login_frequency = login_frequency; - g_object_notify (G_OBJECT (user), "login-frequency"); -} - -static void -gdm_user_set_property (GObject *object, - guint param_id, - const GValue *value, - GParamSpec *pspec) +gdm_user_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) { GdmUser *user; user = GDM_USER (object); - switch (param_id) { - case PROP_MANAGER: - user->manager = g_value_get_object (value); - g_assert (user->manager); - break; - case PROP_LOGIN_FREQUENCY: - _gdm_user_set_login_frequency (user, g_value_get_ulong (value)); - break; + switch (prop_id) { default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void -gdm_user_get_property (GObject *object, - guint param_id, - GValue *value, - GParamSpec *pspec) +gdm_user_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) { GdmUser *user; user = GDM_USER (object); - switch (param_id) { - case PROP_MANAGER: - g_value_set_object (value, user->manager); - break; - case PROP_USER_NAME: - g_value_set_string (value, user->user_name); - break; - case PROP_REAL_NAME: - g_value_set_string (value, user->real_name); - break; - case PROP_HOME_DIR: - g_value_set_string (value, user->home_dir); - break; - case PROP_UID: - g_value_set_ulong (value, user->uid); - break; - case PROP_SHELL: - g_value_set_string (value, user->shell); - break; - case PROP_LOGIN_FREQUENCY: - g_value_set_ulong (value, user->login_frequency); + switch (prop_id) { + case PROP_IS_LOADED: + g_value_set_boolean (value, user->is_loaded); break; default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } @@ -239,70 +189,23 @@ gdm_user_class_init (GdmUserClass *class) gobject_class = G_OBJECT_CLASS (class); + gobject_class->finalize = gdm_user_finalize; gobject_class->set_property = gdm_user_set_property; gobject_class->get_property = gdm_user_get_property; - gobject_class->finalize = gdm_user_finalize; g_object_class_install_property (gobject_class, - PROP_MANAGER, - g_param_spec_object ("manager", - "Manager", - "The user manager object this user is controlled by.", - GDM_TYPE_USER_MANAGER, - (G_PARAM_READWRITE | - G_PARAM_CONSTRUCT_ONLY))); + PROP_IS_LOADED, + g_param_spec_boolean ("is-loaded", + NULL, + NULL, + FALSE, + G_PARAM_READABLE)); - g_object_class_install_property (gobject_class, - PROP_REAL_NAME, - g_param_spec_string ("real-name", - "Real Name", - "The real name to display for this user.", - NULL, - G_PARAM_READABLE)); - - g_object_class_install_property (gobject_class, - PROP_UID, - g_param_spec_ulong ("uid", - "User ID", - "The UID for this user.", - 0, G_MAXULONG, 0, - G_PARAM_READABLE)); - g_object_class_install_property (gobject_class, - PROP_USER_NAME, - g_param_spec_string ("user-name", - "User Name", - "The login name for this user.", - NULL, - G_PARAM_READABLE)); - g_object_class_install_property (gobject_class, - PROP_HOME_DIR, - g_param_spec_string ("home-directory", - "Home Directory", - "The home directory for this user.", - NULL, - G_PARAM_READABLE)); - g_object_class_install_property (gobject_class, - PROP_SHELL, - g_param_spec_string ("shell", - "Shell", - "The shell for this user.", - NULL, - G_PARAM_READABLE)); - g_object_class_install_property (gobject_class, - PROP_LOGIN_FREQUENCY, - g_param_spec_ulong ("login-frequency", - "login frequency", - "login frequency", - 0, - G_MAXULONG, - 0, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); - - signals [ICON_CHANGED] = - g_signal_new ("icon-changed", + signals [CHANGED] = + g_signal_new ("changed", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (GdmUserClass, icon_changed), + 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); @@ -310,74 +213,26 @@ gdm_user_class_init (GdmUserClass *class) g_signal_new ("sessions-changed", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (GdmUserClass, sessions_changed), + 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } - -static void -on_icon_monitor_changed (GFileMonitor *monitor, - GFile *file, - GFile *other_file, - GFileMonitorEvent event_type, - GdmUser *user) -{ - g_debug ("Icon changed: %d", event_type); - - if (event_type != G_FILE_MONITOR_EVENT_CHANGED && - event_type != G_FILE_MONITOR_EVENT_CREATED) { - return; - } - - _gdm_user_icon_changed (user); -} - -static void -update_icon_monitor (GdmUser *user) -{ - GFile *file; - GError *error; - char *path; - - if (user->home_dir == NULL) { - return; - } - - if (user->icon_monitor != NULL) { - g_file_monitor_cancel (user->icon_monitor); - user->icon_monitor = NULL; - } - - path = g_build_filename (user->home_dir, ".face", NULL); - g_debug ("adding monitor for '%s'", path); - file = g_file_new_for_path (path); - error = NULL; - user->icon_monitor = g_file_monitor_file (file, - G_FILE_MONITOR_NONE, - NULL, - &error); - if (user->icon_monitor != NULL) { - g_signal_connect (user->icon_monitor, - "changed", - G_CALLBACK (on_icon_monitor_changed), - user); - } else { - g_warning ("Unable to monitor %s: %s", path, error->message); - g_error_free (error); - } - g_object_unref (file); - g_free (path); -} - static void gdm_user_init (GdmUser *user) { - user->manager = NULL; + GError *error; + user->user_name = NULL; user->real_name = NULL; user->sessions = NULL; + + error = NULL; + user->connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error); + if (user->connection == NULL) { + g_warning ("Couldn't connect to system bus: %s", error->message); + } } static void @@ -387,51 +242,81 @@ gdm_user_finalize (GObject *object) user = GDM_USER (object); - g_file_monitor_cancel (user->icon_monitor); - g_free (user->user_name); g_free (user->real_name); + g_free (user->icon_file); + g_free (user->object_path); + + if (user->accounts_proxy != NULL) { + g_object_unref (user->accounts_proxy); + } + + if (user->object_proxy != NULL) { + g_object_unref (user->object_proxy); + } + + if (user->connection != NULL) { + dbus_g_connection_unref (user->connection); + } if (G_OBJECT_CLASS (gdm_user_parent_class)->finalize) (*G_OBJECT_CLASS (gdm_user_parent_class)->finalize) (object); } +static void +set_is_loaded (GdmUser *user, + gboolean is_loaded) +{ + if (user->is_loaded != is_loaded) { + user->is_loaded = is_loaded; + g_object_notify (G_OBJECT (user), "is-loaded"); + } +} + /** - * _gdm_user_update: + * _gdm_user_update_from_pwent: * @user: the user object to update. * @pwent: the user data to use. * * Updates the properties of @user using the data in @pwent. - * - * Since: 1.0 **/ void -_gdm_user_update (GdmUser *user, - const struct passwd *pwent) +_gdm_user_update_from_pwent (GdmUser *user, + const struct passwd *pwent) { - gchar *real_name; + gchar *real_name = NULL; + gboolean changed; g_return_if_fail (GDM_IS_USER (user)); g_return_if_fail (pwent != NULL); + g_return_if_fail (user->object_path == NULL); - g_object_freeze_notify (G_OBJECT (user)); + changed = FALSE; /* Display Name */ if (pwent->pw_gecos && pwent->pw_gecos[0] != '\0') { - gchar *first_comma; - gchar *real_name_utf8; + gchar *first_comma = NULL; + gchar *valid_utf8_name = NULL; - real_name_utf8 = g_locale_to_utf8 (pwent->pw_gecos, -1, NULL, NULL, NULL); - - first_comma = strchr (real_name_utf8, ','); - if (first_comma) { - real_name = g_strndup (real_name_utf8, first_comma - real_name_utf8); - g_free (real_name_utf8); + if (g_utf8_validate (pwent->pw_gecos, -1, NULL)) { + valid_utf8_name = pwent->pw_gecos; + first_comma = strchr (valid_utf8_name, ','); } else { - real_name = real_name_utf8; + g_warning ("User %s has invalid UTF-8 in GECOS field. " + "It would be a good thing to check /etc/passwd.", + pwent->pw_name ? pwent->pw_name : ""); } - if (real_name[0] == '\0') { + if (first_comma) { + real_name = g_strndup (valid_utf8_name, + (first_comma - valid_utf8_name)); + } else if (valid_utf8_name) { + real_name = g_strdup (valid_utf8_name); + } else { + real_name = NULL; + } + + if (real_name && real_name[0] == '\0') { g_free (real_name); real_name = NULL; } @@ -439,14 +324,10 @@ _gdm_user_update (GdmUser *user, real_name = NULL; } - if ((real_name && !user->real_name) || - (!real_name && user->real_name) || - (real_name && - user->real_name && - strcmp (real_name, user->real_name) != 0)) { + if (g_strcmp0 (real_name, user->real_name) != 0) { g_free (user->real_name); user->real_name = real_name; - g_object_notify (G_OBJECT (user), "real-name"); + changed = TRUE; } else { g_free (real_name); } @@ -454,60 +335,58 @@ _gdm_user_update (GdmUser *user, /* UID */ if (pwent->pw_uid != user->uid) { user->uid = pwent->pw_uid; - g_object_notify (G_OBJECT (user), "uid"); + changed = TRUE; } /* Username */ - if ((pwent->pw_name && !user->user_name) || - (!pwent->pw_name && user->user_name) || - (pwent->pw_name && - user->user_name && - strcmp (user->user_name, pwent->pw_name) != 0)) { + if (g_strcmp0 (pwent->pw_name, user->user_name) != 0) { + g_free (user->icon_file); + user->icon_file = NULL; + if (pwent->pw_name != NULL) { + gboolean res; + + user->icon_file = g_build_filename (GDM_CACHE_DIR, pwent->pw_name, "face", NULL); + + res = check_user_file (user->icon_file); + if (!res) { + g_free (user->icon_file); + user->icon_file = g_build_filename (GLOBAL_FACEDIR, pwent->pw_name, NULL); + } + } + g_free (user->user_name); user->user_name = g_strdup (pwent->pw_name); - g_object_notify (G_OBJECT (user), "user-name"); + changed = TRUE; } - /* Home Directory */ - if ((pwent->pw_dir && !user->home_dir) || - (!pwent->pw_dir && user->home_dir) || - strcmp (user->home_dir, pwent->pw_dir) != 0) { - g_free (user->home_dir); - user->home_dir = g_strdup (pwent->pw_dir); - g_object_notify (G_OBJECT (user), "home-directory"); - g_signal_emit (user, signals[ICON_CHANGED], 0); + if (!user->is_loaded) { + set_is_loaded (user, TRUE); } - /* Shell */ - if ((pwent->pw_shell && !user->shell) || - (!pwent->pw_shell && user->shell) || - (pwent->pw_shell && - user->shell && - strcmp (user->shell, pwent->pw_shell) != 0)) { - g_free (user->shell); - user->shell = g_strdup (pwent->pw_shell); - g_object_notify (G_OBJECT (user), "shell"); + if (changed) { + g_signal_emit (user, signals[CHANGED], 0); } - - update_icon_monitor (user); - - g_object_thaw_notify (G_OBJECT (user)); } /** - * _gdm_user_icon_changed: - * @user: the user to emit the signal for. + * _gdm_user_update_login_frequency: + * @user: the user object to update + * @login_frequency: the number of times the user has logged in * - * Emits the "icon-changed" signal for @user. - * - * Since: 1.0 + * Updates the login frequency of @user **/ void -_gdm_user_icon_changed (GdmUser *user) +_gdm_user_update_login_frequency (GdmUser *user, + guint64 login_frequency) { g_return_if_fail (GDM_IS_USER (user)); - g_signal_emit (user, signals[ICON_CHANGED], 0); + if (login_frequency == user->login_frequency) { + return; + } + + user->login_frequency = login_frequency; + g_signal_emit (user, signals[CHANGED], 0); } /** @@ -516,10 +395,8 @@ _gdm_user_icon_changed (GdmUser *user) * * Retrieves the ID of @user. * - * Returns: a pointer to an array of characters which must not be modified or + * Returns: (transfer none): a pointer to an array of characters which must not be modified or * freed, or %NULL. - * - * Since: 1.0 **/ gulong @@ -536,12 +413,10 @@ gdm_user_get_uid (GdmUser *user) * * Retrieves the display name of @user. * - * Returns: a pointer to an array of characters which must not be modified or + * Returns: (transfer none): a pointer to an array of characters which must not be modified or * freed, or %NULL. - * - * Since: 1.0 **/ -G_CONST_RETURN gchar * +const char * gdm_user_get_real_name (GdmUser *user) { g_return_val_if_fail (GDM_IS_USER (user), NULL); @@ -555,13 +430,11 @@ gdm_user_get_real_name (GdmUser *user) * * Retrieves the login name of @user. * - * Returns: a pointer to an array of characters which must not be modified or + * Returns: (transfer none): a pointer to an array of characters which must not be modified or * freed, or %NULL. - * - * Since: 1.0 **/ -G_CONST_RETURN gchar * +const char * gdm_user_get_user_name (GdmUser *user) { g_return_val_if_fail (GDM_IS_USER (user), NULL); @@ -570,45 +443,13 @@ gdm_user_get_user_name (GdmUser *user) } /** - * gdm_user_get_home_directory: - * @user: the user object to examine. + * gdm_user_get_login_frequency: + * @user: a #GdmUser * - * Retrieves the home directory of @user. + * Returns the number of times @user has logged in. * - * Returns: a pointer to an array of characters which must not be modified or - * freed, or %NULL. - * - * Since: 1.0 - **/ - -G_CONST_RETURN gchar * -gdm_user_get_home_directory (GdmUser *user) -{ - g_return_val_if_fail (GDM_IS_USER (user), NULL); - - return user->home_dir; -} - -/** - * gdm_user_get_shell: - * @user: the user object to examine. - * - * Retrieves the login shell of @user. - * - * Returns: a pointer to an array of characters which must not be modified or - * freed, or %NULL. - * - * Since: 1.0 - **/ - -G_CONST_RETURN gchar * -gdm_user_get_shell (GdmUser *user) -{ - g_return_val_if_fail (GDM_IS_USER (user), NULL); - - return user->shell; -} - + * Returns: the login frequency + */ gulong gdm_user_get_login_frequency (GdmUser *user) { @@ -625,10 +466,36 @@ gdm_user_collate (GdmUser *user1, const char *str2; gulong num1; gulong num2; + guint len1; + guint len2; - g_return_val_if_fail (user1 == NULL || GDM_IS_USER (user1), 0); - g_return_val_if_fail (user2 == NULL || GDM_IS_USER (user2), 0); + g_return_val_if_fail (GDM_IS_USER (user1), 0); + g_return_val_if_fail (GDM_IS_USER (user2), 0); + num1 = user1->login_frequency; + num2 = user2->login_frequency; + + if (num1 > num2) { + return -1; + } + + if (num1 < num2) { + return 1; + } + + + len1 = g_list_length (user1->sessions); + len2 = g_list_length (user2->sessions); + + if (len1 > len2) { + return -1; + } + + if (len1 < len2) { + return 1; + } + + /* if login frequency is equal try names */ if (user1->real_name != NULL) { str1 = user1->real_name; } else { @@ -641,18 +508,6 @@ gdm_user_collate (GdmUser *user1, str2 = user2->user_name; } - num1 = user1->login_frequency; - num2 = user2->login_frequency; - g_debug ("Login freq 1=%u 2=%u", (guint)num1, (guint)num2); - if (num1 > num2) { - return -1; - } - - if (num1 < num2) { - return 1; - } - - /* if login frequency is equal try names */ if (str1 == NULL && str2 != NULL) { return -1; } @@ -669,18 +524,11 @@ gdm_user_collate (GdmUser *user1, } static gboolean -check_user_file (const char *filename, - uid_t user, - gssize max_file_size, - gboolean relax_group, - gboolean relax_other) +check_user_file (const char *filename) { + gssize max_file_size = MAX_FILE_SIZE; struct stat fileinfo; - if (max_file_size < 0) { - max_file_size = G_MAXSIZE; - } - /* Exists/Readable? */ if (stat (filename, &fileinfo) < 0) { return FALSE; @@ -691,21 +539,6 @@ check_user_file (const char *filename, return FALSE; } - /* Owned by user? */ - if (G_UNLIKELY (fileinfo.st_uid != user)) { - return FALSE; - } - - /* Group not writable or relax_group? */ - if (G_UNLIKELY ((fileinfo.st_mode & S_IWGRP) == S_IWGRP && !relax_group)) { - return FALSE; - } - - /* Other not writable or relax_other? */ - if (G_UNLIKELY ((fileinfo.st_mode & S_IWOTH) == S_IWOTH && !relax_other)) { - return FALSE; - } - /* Size is kosher? */ if (G_UNLIKELY (fileinfo.st_size > max_file_size)) { return FALSE; @@ -714,224 +547,46 @@ check_user_file (const char *filename, return TRUE; } -static char * -get_filesystem_type (const char *path) -{ - GFile *file; - GFileInfo *file_info; - GError *error; - char *filesystem_type; - - file = g_file_new_for_path (path); - error = NULL; - file_info = g_file_query_filesystem_info (file, - G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, - NULL, - &error); - if (file_info == NULL) { - g_warning ("Unable to query filesystem type for %s: %s", path, error->message); - g_error_free (error); - g_object_unref (file); - return NULL; - } - - filesystem_type = g_strdup (g_file_info_get_attribute_string (file_info, - G_FILE_ATTRIBUTE_FILESYSTEM_TYPE)); - if (filesystem_type == NULL) { - g_warning ("GIO returned NULL filesystem type for %s", path); - } - - g_object_unref (file); - g_object_unref (file_info); - - return filesystem_type; -} - -static GdkPixbuf * -render_icon_from_home (GdmUser *user, - int icon_size) -{ - GdkPixbuf *retval; - char *path; - gboolean is_local; - gboolean is_autofs; - gboolean res; - char *filesystem_type; - - is_local = FALSE; - - /* special case: look at parent of home to detect autofs - this is so we don't try to trigger an automount */ - path = g_path_get_dirname (user->home_dir); - filesystem_type = get_filesystem_type (path); - is_autofs = (filesystem_type != NULL && strcmp (filesystem_type, "autofs") == 0); - g_free (filesystem_type); - g_free (path); - - if (is_autofs) { - return NULL; - } - - /* now check that home dir itself is local */ - filesystem_type = get_filesystem_type (user->home_dir); - is_local = ((filesystem_type != NULL) && - (strcmp (filesystem_type, "nfs") != 0) && - (strcmp (filesystem_type, "afs") != 0) && - (strcmp (filesystem_type, "autofs") != 0) && - (strcmp (filesystem_type, "unknown") != 0) && - (strcmp (filesystem_type, "ncpfs") != 0)); - g_free (filesystem_type); - - /* only look at local home directories so we don't try to - read from remote (e.g. NFS) volumes */ - if (! is_local) { - return NULL; - } - - /* First, try "~/.face" */ - path = g_build_filename (user->home_dir, ".face", NULL); - res = check_user_file (path, - user->uid, - MAX_FILE_SIZE, - RELAX_GROUP, - RELAX_OTHER); - if (res) { - retval = gdk_pixbuf_new_from_file_at_size (path, - icon_size, - icon_size, - NULL); - } else { - retval = NULL; - } - g_free (path); - - /* Next, try "~/.face.icon" */ - if (retval == NULL) { - path = g_build_filename (user->home_dir, - ".face.icon", - NULL); - res = check_user_file (path, - user->uid, - MAX_FILE_SIZE, - RELAX_GROUP, - RELAX_OTHER); - if (res) { - retval = gdk_pixbuf_new_from_file_at_size (path, - icon_size, - icon_size, - NULL); - } else { - retval = NULL; - } - - g_free (path); - } - - /* Still nothing, try the user's personal GDM config */ - if (retval == NULL) { - path = g_build_filename (user->home_dir, - ".gnome", - "gdm", - NULL); - res = check_user_file (path, - user->uid, - MAX_FILE_SIZE, - RELAX_GROUP, - RELAX_OTHER); - if (res) { - GKeyFile *keyfile; - char *icon_path; - - keyfile = g_key_file_new (); - g_key_file_load_from_file (keyfile, - path, - G_KEY_FILE_NONE, - NULL); - - icon_path = g_key_file_get_string (keyfile, - "face", - "picture", - NULL); - res = check_user_file (icon_path, - user->uid, - MAX_FILE_SIZE, - RELAX_GROUP, - RELAX_OTHER); - if (icon_path && res) { - retval = gdk_pixbuf_new_from_file_at_size (path, - icon_size, - icon_size, - NULL); - } else { - retval = NULL; - } - - g_free (icon_path); - g_key_file_free (keyfile); - } else { - retval = NULL; - } - - g_free (path); - } - - return retval; -} - static void -curved_rectangle (cairo_t *cr, - double x0, - double y0, - double width, - double height, - double radius) +rounded_rectangle (cairo_t *cr, + gdouble aspect, + gdouble x, + gdouble y, + gdouble corner_radius, + gdouble width, + gdouble height) { - double x1; - double y1; + gdouble radius; + gdouble degrees; - x1 = x0 + width; - y1 = y0 + height; - - if (width < FLT_EPSILON || height < FLT_EPSILON) { - return; - } - - if (width / 2 < radius) { - if (height / 2 < radius) { - cairo_move_to (cr, x0, (y0 + y1) / 2); - cairo_curve_to (cr, x0 ,y0, x0, y0, (x0 + x1) / 2, y0); - cairo_curve_to (cr, x1, y0, x1, y0, x1, (y0 + y1) / 2); - cairo_curve_to (cr, x1, y1, x1, y1, (x1 + x0) / 2, y1); - cairo_curve_to (cr, x0, y1, x0, y1, x0, (y0 + y1) / 2); - } else { - cairo_move_to (cr, x0, y0 + radius); - cairo_curve_to (cr, x0, y0, x0, y0, (x0 + x1) / 2, y0); - cairo_curve_to (cr, x1, y0, x1, y0, x1, y0 + radius); - cairo_line_to (cr, x1, y1 - radius); - cairo_curve_to (cr, x1, y1, x1, y1, (x1 + x0) / 2, y1); - cairo_curve_to (cr, x0, y1, x0, y1, x0, y1 - radius); - } - } else { - if (height / 2 < radius) { - cairo_move_to (cr, x0, (y0 + y1) / 2); - cairo_curve_to (cr, x0, y0, x0 , y0, x0 + radius, y0); - cairo_line_to (cr, x1 - radius, y0); - cairo_curve_to (cr, x1, y0, x1, y0, x1, (y0 + y1) / 2); - cairo_curve_to (cr, x1, y1, x1, y1, x1 - radius, y1); - cairo_line_to (cr, x0 + radius, y1); - cairo_curve_to (cr, x0, y1, x0, y1, x0, (y0 + y1) / 2); - } else { - cairo_move_to (cr, x0, y0 + radius); - cairo_curve_to (cr, x0 , y0, x0 , y0, x0 + radius, y0); - cairo_line_to (cr, x1 - radius, y0); - cairo_curve_to (cr, x1, y0, x1, y0, x1, y0 + radius); - cairo_line_to (cr, x1, y1 - radius); - cairo_curve_to (cr, x1, y1, x1, y1, x1 - radius, y1); - cairo_line_to (cr, x0 + radius, y1); - cairo_curve_to (cr, x0, y1, x0, y1, x0, y1 - radius); - } - } + radius = corner_radius / aspect; + degrees = G_PI / 180.0; + cairo_new_sub_path (cr); + cairo_arc (cr, + x + width - radius, + y + radius, + radius, + -90 * degrees, + 0 * degrees); + cairo_arc (cr, + x + width - radius, + y + height - radius, + radius, + 0 * degrees, + 90 * degrees); + cairo_arc (cr, + x + radius, + y + height - radius, + radius, + 90 * degrees, + 180 * degrees); + cairo_arc (cr, + x + radius, + y + radius, + radius, + 180 * degrees, + 270 * degrees); cairo_close_path (cr); } @@ -1065,7 +720,7 @@ frame_pixbuf (GdkPixbuf *source) w = gdk_pixbuf_get_width (source) + frame_width * 2; h = gdk_pixbuf_get_height (source) + frame_width * 2; - radius = w / 3.0; + radius = w / 10; dest = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, @@ -1090,9 +745,13 @@ frame_pixbuf (GdkPixbuf *source) cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.0); cairo_fill (cr); - curved_rectangle (cr, frame_width, frame_width, - w - frame_width * 2, h - frame_width * 2, - radius); + rounded_rectangle (cr, + 1.0, + frame_width + 0.5, + frame_width + 0.5, + radius, + w - frame_width * 2 - 1, + h - frame_width * 2 - 1); cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 0.3); cairo_fill_preserve (cr); @@ -1110,11 +769,28 @@ frame_pixbuf (GdkPixbuf *source) } /** - * gdm_user_render_icon: - * @user: a #GdmUser: - * @icon_size: icon size in pixels + * gdm_user_is_logged_in: + * @user: a #GdmUser * - * Returns: (transfer full): A new icon for the user + * Returns whether or not #GdmUser is currently logged in. + * + * Returns: %TRUE or %FALSE + */ +gboolean +gdm_user_is_logged_in (GdmUser *user) +{ + return user->sessions != NULL; +} + +/** + * gdm_user_render_icon: + * @user: a #GdmUser + * @icon_size: the size to render the icon at + * + * Returns a #GdkPixbuf of the account icon belonging to @user + * at the pixel size specified by @icon_size. + * + * Returns: (transfer full): a #GdkPixbuf */ GdkPixbuf * gdm_user_render_icon (GdmUser *user, @@ -1122,59 +798,40 @@ gdm_user_render_icon (GdmUser *user, { GdkPixbuf *pixbuf; GdkPixbuf *framed; - char *path; - char *tmp; gboolean res; + GError *error; g_return_val_if_fail (GDM_IS_USER (user), NULL); g_return_val_if_fail (icon_size > 12, NULL); - path = NULL; + pixbuf = NULL; + if (user->icon_file) { + res = check_user_file (user->icon_file); + if (res) { + pixbuf = gdk_pixbuf_new_from_file_at_size (user->icon_file, + icon_size, + icon_size, + NULL); + } else { + pixbuf = NULL; + } + } - pixbuf = render_icon_from_home (user, icon_size); if (pixbuf != NULL) { goto out; } - /* Try ${GlobalFaceDir}/${username} */ - path = g_build_filename (GLOBAL_FACEDIR, user->user_name, NULL); - res = check_user_file (path, - user->uid, - MAX_FILE_SIZE, - RELAX_GROUP, - RELAX_OTHER); - if (res) { - pixbuf = gdk_pixbuf_new_from_file_at_size (path, - icon_size, - icon_size, - NULL); - } else { - pixbuf = NULL; - } + error = NULL; + pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), - g_free (path); - if (pixbuf != NULL) { - goto out; + "avatar-default", + icon_size, + GTK_ICON_LOOKUP_FORCE_SIZE, + &error); + if (error) { + g_warning ("%s", error->message); + g_error_free (error); } - - /* Finally, ${GlobalFaceDir}/${username}.png */ - tmp = g_strconcat (user->user_name, ".png", NULL); - path = g_build_filename (GLOBAL_FACEDIR, tmp, NULL); - g_free (tmp); - res = check_user_file (path, - user->uid, - MAX_FILE_SIZE, - RELAX_GROUP, - RELAX_OTHER); - if (res) { - pixbuf = gdk_pixbuf_new_from_file_at_size (path, - icon_size, - icon_size, - NULL); - } else { - pixbuf = NULL; - } - g_free (path); out: if (pixbuf != NULL) { @@ -1187,3 +844,253 @@ gdm_user_render_icon (GdmUser *user, return pixbuf; } + +/** + * gdm_user_get_icon_file: + * @user: a #GdmUser + * + * Returns the path to the account icon belonging to @user. + * + * Returns: (transfer none): a path to an icon + */ +const char * +gdm_user_get_icon_file (GdmUser *user) +{ + g_return_val_if_fail (GDM_IS_USER (user), NULL); + + return user->icon_file; +} + +/** + * gdm_user_get_object_path: + * @user: a #GdmUser + * + * Returns the user accounts service object path of @user, + * or %NULL if @user doesn't have an object path associated + * with it. + * + * Returns: (transfer none): the primary ConsoleKit session id of the user + */ +const char * +gdm_user_get_object_path (GdmUser *user) +{ + g_return_val_if_fail (GDM_IS_USER (user), NULL); + + return user->object_path; +} + +/** + * gdm_user_get_primary_session_id: + * @user: a #GdmUser + * + * Returns the primary ConsoleKit session id of @user, or %NULL if @user isn't + * logged in. + * + * Returns: (transfer none): the primary ConsoleKit session id of the user + */ +const char * +gdm_user_get_primary_session_id (GdmUser *user) +{ + if (!gdm_user_is_logged_in (user)) { + g_debug ("User %s is not logged in, so has no primary session", + gdm_user_get_user_name (user)); + return NULL; + } + + /* FIXME: better way to choose? */ + return user->sessions->data; +} + +static void +collect_props (const gchar *key, + const GValue *value, + GdmUser *user) +{ + gboolean handled = TRUE; + + if (strcmp (key, "Uid") == 0) { + user->uid = g_value_get_uint64 (value); + } else if (strcmp (key, "UserName") == 0) { + g_free (user->user_name); + user->user_name = g_value_dup_string (value); + } else if (strcmp (key, "RealName") == 0) { + g_free (user->real_name); + user->real_name = g_value_dup_string (value); + } else if (strcmp (key, "AccountType") == 0) { + /* ignore */ + } else if (strcmp (key, "Email") == 0) { + /* ignore */ + } else if (strcmp (key, "Language") == 0) { + /* ignore */ + } else if (strcmp (key, "Location") == 0) { + /* ignore */ + } else if (strcmp (key, "LoginFrequency") == 0) { + user->login_frequency = g_value_get_uint64 (value); + } else if (strcmp (key, "IconFile") == 0) { + gboolean res; + + g_free (user->icon_file); + user->icon_file = g_value_dup_string (value); + + res = check_user_file (user->icon_file); + if (!res) { + g_free (user->icon_file); + user->icon_file = g_build_filename (GLOBAL_FACEDIR, user->user_name, NULL); + } + } else if (strcmp (key, "Locked") == 0) { + /* ignore */ + } else if (strcmp (key, "AutomaticLogin") == 0) { + /* ignore */ + } else if (strcmp (key, "PasswordMode") == 0) { + /* ignore */ + } else if (strcmp (key, "PasswordHint") == 0) { + /* ignore */ + } else if (strcmp (key, "HomeDirectory") == 0) { + /* ignore */ + } else if (strcmp (key, "Shell") == 0) { + /* ignore */ + } else { + handled = FALSE; + } + + if (!handled) { + g_debug ("unhandled property %s", key); + } +} + +static void +on_get_all_finished (DBusGProxy *proxy, + DBusGProxyCall *call, + GdmUser *user) +{ + GError *error; + GHashTable *hash_table; + gboolean res; + + g_assert (user->get_all_call == call); + g_assert (user->object_proxy == proxy); + + error = NULL; + res = dbus_g_proxy_end_call (proxy, + call, + &error, + dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE), + &hash_table, + G_TYPE_INVALID); + user->get_all_call = NULL; + user->object_proxy = NULL; + + if (! res) { + g_debug ("Error calling GetAll() when retrieving properties for %s: %s", + user->object_path, error->message); + g_error_free (error); + goto out; + } + g_hash_table_foreach (hash_table, (GHFunc) collect_props, user); + g_hash_table_unref (hash_table); + + if (!user->is_loaded) { + set_is_loaded (user, TRUE); + } + + g_signal_emit (user, signals[CHANGED], 0); + +out: + g_object_unref (proxy); +} + +static gboolean +update_info (GdmUser *user) +{ + DBusGProxy *proxy; + DBusGProxyCall *call; + + proxy = dbus_g_proxy_new_for_name (user->connection, + ACCOUNTS_NAME, + user->object_path, + DBUS_INTERFACE_PROPERTIES); + + call = dbus_g_proxy_begin_call (proxy, + "GetAll", + (DBusGProxyCallNotify) + on_get_all_finished, + user, + NULL, + G_TYPE_STRING, + ACCOUNTS_USER_INTERFACE, + G_TYPE_INVALID); + + if (call == NULL) { + g_warning ("GdmUser: failed to make GetAll call"); + goto failed; + } + + user->get_all_call = call; + user->object_proxy = proxy; + return TRUE; + +failed: + if (proxy != NULL) { + g_object_unref (proxy); + } + + return FALSE; +} + +static void +changed_handler (DBusGProxy *proxy, + gpointer *data) +{ + GdmUser *user = GDM_USER (data); + + update_info (user); +} + +/** + * _gdm_user_update_from_object_path: + * @user: the user object to update. + * @object_path: the object path of the user to use. + * + * Updates the properties of @user from the accounts service via + * the object path in @object_path. + **/ +void +_gdm_user_update_from_object_path (GdmUser *user, + const char *object_path) +{ + g_return_if_fail (GDM_IS_USER (user)); + g_return_if_fail (object_path != NULL); + g_return_if_fail (user->object_path == NULL); + + user->object_path = g_strdup (object_path); + + user->accounts_proxy = dbus_g_proxy_new_for_name (user->connection, + ACCOUNTS_NAME, + user->object_path, + ACCOUNTS_USER_INTERFACE); + dbus_g_proxy_set_default_timeout (user->accounts_proxy, INT_MAX); + dbus_g_proxy_add_signal (user->accounts_proxy, "Changed", G_TYPE_INVALID); + + dbus_g_proxy_connect_signal (user->accounts_proxy, "Changed", + G_CALLBACK (changed_handler), user, NULL); + + if (!update_info (user)) { + g_warning ("Couldn't update info for user with object path %s", object_path); + } +} + +/** + * gdm_user_is_loaded: + * @user: a #GdmUser + * + * Determines whether or not the user object is loaded and ready to read from. + * #GdmUserManager:is-loaded property must be %TRUE before calling + * gdm_user_manager_list_users() + * + * Returns: %TRUE or %FALSE + */ +gboolean +gdm_user_is_loaded (GdmUser *user) +{ + return user->is_loaded; +} diff --git a/src/gdmuser/gdm-user.h b/src/gdmuser/gdm-user.h index f9063e21a..2f2616ba5 100644 --- a/src/gdmuser/gdm-user.h +++ b/src/gdmuser/gdm-user.h @@ -22,8 +22,8 @@ * Facade object for user data, owned by GdmUserManager */ -#ifndef __GDM_USER_H -#define __GDM_USER_H +#ifndef __GDM_USER_H__ +#define __GDM_USER_H__ #include #include @@ -40,20 +40,24 @@ typedef struct _GdmUserClass GdmUserClass; GType gdm_user_get_type (void) G_GNUC_CONST; +GdmUser *gdm_user_new_from_object_path (const char *path); +const char *gdm_user_get_object_path (GdmUser *user); + gulong gdm_user_get_uid (GdmUser *user); -G_CONST_RETURN char *gdm_user_get_user_name (GdmUser *user); -G_CONST_RETURN char *gdm_user_get_real_name (GdmUser *user); -G_CONST_RETURN char *gdm_user_get_home_directory (GdmUser *user); -G_CONST_RETURN char *gdm_user_get_shell (GdmUser *user); +const char *gdm_user_get_user_name (GdmUser *user); +const char *gdm_user_get_real_name (GdmUser *user); guint gdm_user_get_num_sessions (GdmUser *user); -GList *gdm_user_get_sessions (GdmUser *user); +gboolean gdm_user_is_logged_in (GdmUser *user); gulong gdm_user_get_login_frequency (GdmUser *user); +const char *gdm_user_get_icon_file (GdmUser *user); +const char *gdm_user_get_primary_session_id (GdmUser *user); GdkPixbuf *gdm_user_render_icon (GdmUser *user, gint icon_size); gint gdm_user_collate (GdmUser *user1, GdmUser *user2); +gboolean gdm_user_is_loaded (GdmUser *user); G_END_DECLS diff --git a/src/gnome-shell-plugin.c b/src/gnome-shell-plugin.c index 91626f9f1..f2f6137da 100644 --- a/src/gnome-shell-plugin.c +++ b/src/gnome-shell-plugin.c @@ -296,6 +296,15 @@ add_statistics (GnomeShellPlugin *shell_plugin) NULL, NULL); } +static void +gvc_muted_debug_log_handler (const char *log_domain, + GLogLevelFlags log_level, + const char *message, + gpointer data) +{ + /* Intentionally empty to discard message */ +} + static void gnome_shell_plugin_start (MutterPlugin *plugin) { @@ -355,6 +364,10 @@ gnome_shell_plugin_start (MutterPlugin *plugin) shell_plugin->gjs_context = gjs_context_new_with_search_path(search_path); g_strfreev(search_path); + /* Disable the gnome-volume-control debug */ + g_log_set_handler ("Gvc", G_LOG_LEVEL_DEBUG, + gvc_muted_debug_log_handler, NULL); + /* Initialize the global object here. */ global = shell_global_get (); diff --git a/src/gvc/gvc-channel-map-private.h b/src/gvc/gvc-channel-map-private.h new file mode 100644 index 000000000..3949de3a6 --- /dev/null +++ b/src/gvc/gvc-channel-map-private.h @@ -0,0 +1,39 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __GVC_CHANNEL_MAP_PRIVATE_H +#define __GVC_CHANNEL_MAP_PRIVATE_H + +#include +#include + +G_BEGIN_DECLS + +GvcChannelMap * gvc_channel_map_new_from_pa_channel_map (const pa_channel_map *map); +const pa_channel_map * gvc_channel_map_get_pa_channel_map (const GvcChannelMap *map); + +void gvc_channel_map_volume_changed (GvcChannelMap *map, + const pa_cvolume *cv, + gboolean set); +const pa_cvolume * gvc_channel_map_get_cvolume (const GvcChannelMap *map); + +G_END_DECLS + +#endif /* __GVC_CHANNEL_MAP_PRIVATE_H */ diff --git a/src/gvc/gvc-channel-map.c b/src/gvc/gvc-channel-map.c new file mode 100644 index 000000000..a2073fd33 --- /dev/null +++ b/src/gvc/gvc-channel-map.c @@ -0,0 +1,254 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 William Jon McCann + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include "config.h" + +#include +#include +#include + +#include +#include + +#include + +#include "gvc-channel-map.h" +#include "gvc-channel-map-private.h" + +#define GVC_CHANNEL_MAP_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_CHANNEL_MAP, GvcChannelMapPrivate)) + +struct GvcChannelMapPrivate +{ + pa_channel_map pa_map; + gboolean pa_volume_is_set; + pa_cvolume pa_volume; + gdouble extern_volume[NUM_TYPES]; /* volume, balance, fade, lfe */ + gboolean can_balance; + gboolean can_fade; +}; + +enum { + VOLUME_CHANGED, + LAST_SIGNAL +}; + +static guint signals [LAST_SIGNAL] = { 0, }; + +static void gvc_channel_map_class_init (GvcChannelMapClass *klass); +static void gvc_channel_map_init (GvcChannelMap *channel_map); +static void gvc_channel_map_finalize (GObject *object); + +G_DEFINE_TYPE (GvcChannelMap, gvc_channel_map, G_TYPE_OBJECT) + +guint +gvc_channel_map_get_num_channels (const GvcChannelMap *map) +{ + g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), 0); + + if (!pa_channel_map_valid(&map->priv->pa_map)) + return 0; + + return map->priv->pa_map.channels; +} + +const gdouble * +gvc_channel_map_get_volume (GvcChannelMap *map) +{ + g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), NULL); + + if (!pa_channel_map_valid(&map->priv->pa_map)) + return NULL; + + map->priv->extern_volume[VOLUME] = (gdouble) pa_cvolume_max (&map->priv->pa_volume); + if (gvc_channel_map_can_balance (map)) + map->priv->extern_volume[BALANCE] = (gdouble) pa_cvolume_get_balance (&map->priv->pa_volume, &map->priv->pa_map); + else + map->priv->extern_volume[BALANCE] = 0; + if (gvc_channel_map_can_fade (map)) + map->priv->extern_volume[FADE] = (gdouble) pa_cvolume_get_fade (&map->priv->pa_volume, &map->priv->pa_map); + else + map->priv->extern_volume[FADE] = 0; + if (gvc_channel_map_has_lfe (map)) + map->priv->extern_volume[LFE] = (gdouble) pa_cvolume_get_position (&map->priv->pa_volume, &map->priv->pa_map, PA_CHANNEL_POSITION_LFE); + else + map->priv->extern_volume[LFE] = 0; + + return map->priv->extern_volume; +} + +gboolean +gvc_channel_map_can_balance (const GvcChannelMap *map) +{ + g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), FALSE); + + return map->priv->can_balance; +} + +gboolean +gvc_channel_map_can_fade (const GvcChannelMap *map) +{ + g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), FALSE); + + return map->priv->can_fade; +} + +const char * +gvc_channel_map_get_mapping (const GvcChannelMap *map) +{ + g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), NULL); + + if (!pa_channel_map_valid(&map->priv->pa_map)) + return NULL; + + return pa_channel_map_to_pretty_name (&map->priv->pa_map); +} + +/** + * gvc_channel_map_has_position: (skip) + * + * @map: + * @position: + * + * Returns: + */ +gboolean +gvc_channel_map_has_position (const GvcChannelMap *map, + pa_channel_position_t position) +{ + g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), FALSE); + + return pa_channel_map_has_position (&(map->priv->pa_map), position); +} + +const pa_channel_map * +gvc_channel_map_get_pa_channel_map (const GvcChannelMap *map) +{ + g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), NULL); + + if (!pa_channel_map_valid(&map->priv->pa_map)) + return NULL; + + return &map->priv->pa_map; +} + +const pa_cvolume * +gvc_channel_map_get_cvolume (const GvcChannelMap *map) +{ + g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), NULL); + + if (!pa_channel_map_valid(&map->priv->pa_map)) + return NULL; + + return &map->priv->pa_volume; +} + +static void +gvc_channel_map_class_init (GvcChannelMapClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = gvc_channel_map_finalize; + + signals [VOLUME_CHANGED] = + g_signal_new ("volume-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GvcChannelMapClass, volume_changed), + NULL, NULL, + g_cclosure_marshal_VOID__BOOLEAN, + G_TYPE_NONE, 1, G_TYPE_BOOLEAN); + + g_type_class_add_private (klass, sizeof (GvcChannelMapPrivate)); +} + +void +gvc_channel_map_volume_changed (GvcChannelMap *map, + const pa_cvolume *cv, + gboolean set) +{ + g_return_if_fail (GVC_IS_CHANNEL_MAP (map)); + g_return_if_fail (cv != NULL); + g_return_if_fail (pa_cvolume_compatible_with_channel_map(cv, &map->priv->pa_map)); + + if (pa_cvolume_equal(cv, &map->priv->pa_volume)) + return; + + map->priv->pa_volume = *cv; + + if (map->priv->pa_volume_is_set == FALSE) { + map->priv->pa_volume_is_set = TRUE; + return; + } + g_signal_emit (map, signals[VOLUME_CHANGED], 0, set); +} + +static void +gvc_channel_map_init (GvcChannelMap *map) +{ + map->priv = GVC_CHANNEL_MAP_GET_PRIVATE (map); + map->priv->pa_volume_is_set = FALSE; +} + +static void +gvc_channel_map_finalize (GObject *object) +{ + GvcChannelMap *channel_map; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_CHANNEL_MAP (object)); + + channel_map = GVC_CHANNEL_MAP (object); + + g_return_if_fail (channel_map->priv != NULL); + + G_OBJECT_CLASS (gvc_channel_map_parent_class)->finalize (object); +} + +GvcChannelMap * +gvc_channel_map_new (void) +{ + GObject *map; + map = g_object_new (GVC_TYPE_CHANNEL_MAP, NULL); + return GVC_CHANNEL_MAP (map); +} + +static void +set_from_pa_map (GvcChannelMap *map, + const pa_channel_map *pa_map) +{ + g_assert (pa_channel_map_valid(pa_map)); + + map->priv->can_balance = pa_channel_map_can_balance (pa_map); + map->priv->can_fade = pa_channel_map_can_fade (pa_map); + + map->priv->pa_map = *pa_map; + pa_cvolume_set(&map->priv->pa_volume, pa_map->channels, PA_VOLUME_NORM); +} + +GvcChannelMap * +gvc_channel_map_new_from_pa_channel_map (const pa_channel_map *pa_map) +{ + GObject *map; + map = g_object_new (GVC_TYPE_CHANNEL_MAP, NULL); + + set_from_pa_map (GVC_CHANNEL_MAP (map), pa_map); + + return GVC_CHANNEL_MAP (map); +} diff --git a/src/gvc/gvc-channel-map.h b/src/gvc/gvc-channel-map.h new file mode 100644 index 000000000..85c577289 --- /dev/null +++ b/src/gvc/gvc-channel-map.h @@ -0,0 +1,73 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __GVC_CHANNEL_MAP_H +#define __GVC_CHANNEL_MAP_H + +#include +#include + +G_BEGIN_DECLS + +#define GVC_TYPE_CHANNEL_MAP (gvc_channel_map_get_type ()) +#define GVC_CHANNEL_MAP(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_CHANNEL_MAP, GvcChannelMap)) +#define GVC_CHANNEL_MAP_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_CHANNEL_MAP, GvcChannelMapClass)) +#define GVC_IS_CHANNEL_MAP(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_CHANNEL_MAP)) +#define GVC_IS_CHANNEL_MAP_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_CHANNEL_MAP)) +#define GVC_CHANNEL_MAP_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_CHANNEL_MAP, GvcChannelMapClass)) + +typedef struct GvcChannelMapPrivate GvcChannelMapPrivate; + +typedef struct +{ + GObject parent; + GvcChannelMapPrivate *priv; +} GvcChannelMap; + +typedef struct +{ + GObjectClass parent_class; + void (*volume_changed) (GvcChannelMap *channel_map, gboolean set); +} GvcChannelMapClass; + +enum { + VOLUME, + BALANCE, + FADE, + LFE, + NUM_TYPES +}; + +GType gvc_channel_map_get_type (void); + +GvcChannelMap * gvc_channel_map_new (void); +guint gvc_channel_map_get_num_channels (const GvcChannelMap *map); +const gdouble * gvc_channel_map_get_volume (GvcChannelMap *map); +gboolean gvc_channel_map_can_balance (const GvcChannelMap *map); +gboolean gvc_channel_map_can_fade (const GvcChannelMap *map); +gboolean gvc_channel_map_has_position (const GvcChannelMap *map, + pa_channel_position_t position); +#define gvc_channel_map_has_lfe(x) gvc_channel_map_has_position (x, PA_CHANNEL_POSITION_LFE) + +const char * gvc_channel_map_get_mapping (const GvcChannelMap *map); + +G_END_DECLS + +#endif /* __GVC_CHANNEL_MAP_H */ diff --git a/src/gvc/gvc-mixer-card-private.h b/src/gvc/gvc-mixer-card-private.h new file mode 100644 index 000000000..e190f7f4b --- /dev/null +++ b/src/gvc/gvc-mixer-card-private.h @@ -0,0 +1,35 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008-2009 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __GVC_MIXER_CARD_PRIVATE_H +#define __GVC_MIXER_CARD_PRIVATE_H + +#include +#include "gvc-mixer-card.h" + +G_BEGIN_DECLS + +GvcMixerCard * gvc_mixer_card_new (pa_context *context, + guint index); +pa_context * gvc_mixer_card_get_pa_context (GvcMixerCard *card); + +G_END_DECLS + +#endif /* __GVC_MIXER_CARD_PRIVATE_H */ diff --git a/src/gvc/gvc-mixer-card.c b/src/gvc/gvc-mixer-card.c new file mode 100644 index 000000000..f198f1b1c --- /dev/null +++ b/src/gvc/gvc-mixer-card.c @@ -0,0 +1,506 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 William Jon McCann + * Copyright (C) 2009 Bastien Nocera + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include "config.h" + +#include +#include +#include + +#include +#include + +#include + +#include "gvc-mixer-card.h" +#include "gvc-mixer-card-private.h" + +#define GVC_MIXER_CARD_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_CARD, GvcMixerCardPrivate)) + +static guint32 card_serial = 1; + +struct GvcMixerCardPrivate +{ + pa_context *pa_context; + guint id; + guint index; + char *name; + char *icon_name; + char *profile; + char *target_profile; + char *human_profile; + GList *profiles; + pa_operation *profile_op; +}; + +enum +{ + PROP_0, + PROP_ID, + PROP_PA_CONTEXT, + PROP_INDEX, + PROP_NAME, + PROP_ICON_NAME, + PROP_PROFILE, + PROP_HUMAN_PROFILE, +}; + +static void gvc_mixer_card_class_init (GvcMixerCardClass *klass); +static void gvc_mixer_card_init (GvcMixerCard *mixer_card); +static void gvc_mixer_card_finalize (GObject *object); + +G_DEFINE_TYPE (GvcMixerCard, gvc_mixer_card, G_TYPE_OBJECT) + +static guint32 +get_next_card_serial (void) +{ + guint32 serial; + + serial = card_serial++; + + if ((gint32)card_serial < 0) { + card_serial = 1; + } + + return serial; +} + +pa_context * +gvc_mixer_card_get_pa_context (GvcMixerCard *card) +{ + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), 0); + return card->priv->pa_context; +} + +guint +gvc_mixer_card_get_index (GvcMixerCard *card) +{ + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), 0); + return card->priv->index; +} + +guint +gvc_mixer_card_get_id (GvcMixerCard *card) +{ + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), 0); + return card->priv->id; +} + +const char * +gvc_mixer_card_get_name (GvcMixerCard *card) +{ + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), NULL); + return card->priv->name; +} + +gboolean +gvc_mixer_card_set_name (GvcMixerCard *card, + const char *name) +{ + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), FALSE); + + g_free (card->priv->name); + card->priv->name = g_strdup (name); + g_object_notify (G_OBJECT (card), "name"); + + return TRUE; +} + +const char * +gvc_mixer_card_get_icon_name (GvcMixerCard *card) +{ + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), NULL); + return card->priv->icon_name; +} + +gboolean +gvc_mixer_card_set_icon_name (GvcMixerCard *card, + const char *icon_name) +{ + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), FALSE); + + g_free (card->priv->icon_name); + card->priv->icon_name = g_strdup (icon_name); + g_object_notify (G_OBJECT (card), "icon-name"); + + return TRUE; +} + +/** + * gvc_mixer_card_get_profile: (skip) + * + * @card: + * + * Returns: + */ +GvcMixerCardProfile * +gvc_mixer_card_get_profile (GvcMixerCard *card) +{ + GList *l; + + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), NULL); + g_return_val_if_fail (card->priv->profiles != NULL, FALSE); + + for (l = card->priv->profiles; l != NULL; l = l->next) { + GvcMixerCardProfile *p = l->data; + if (g_str_equal (card->priv->profile, p->profile)) { + return p; + } + } + + g_assert_not_reached (); + + return NULL; +} + +gboolean +gvc_mixer_card_set_profile (GvcMixerCard *card, + const char *profile) +{ + GList *l; + + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), FALSE); + g_return_val_if_fail (card->priv->profiles != NULL, FALSE); + + g_free (card->priv->profile); + card->priv->profile = g_strdup (profile); + + g_free (card->priv->human_profile); + card->priv->human_profile = NULL; + + for (l = card->priv->profiles; l != NULL; l = l->next) { + GvcMixerCardProfile *p = l->data; + if (g_str_equal (card->priv->profile, p->profile)) { + card->priv->human_profile = g_strdup (p->human_profile); + break; + } + } + + g_object_notify (G_OBJECT (card), "profile"); + + return TRUE; +} + +static void +_pa_context_set_card_profile_by_index_cb (pa_context *context, + int success, + void *userdata) +{ + GvcMixerCard *card = GVC_MIXER_CARD (userdata); + + g_assert (card->priv->target_profile); + + if (success > 0) { + gvc_mixer_card_set_profile (card, card->priv->target_profile); + } else { + g_debug ("Failed to switch profile on '%s' from '%s' to '%s'", + card->priv->name, + card->priv->profile, + card->priv->target_profile); + } + g_free (card->priv->target_profile); + card->priv->target_profile = NULL; + + pa_operation_unref (card->priv->profile_op); + card->priv->profile_op = NULL; +} + +gboolean +gvc_mixer_card_change_profile (GvcMixerCard *card, + const char *profile) +{ + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), FALSE); + g_return_val_if_fail (card->priv->profiles != NULL, FALSE); + + /* Same profile, or already requested? */ + if (g_strcmp0 (card->priv->profile, profile) == 0) + return TRUE; + if (g_strcmp0 (profile, card->priv->target_profile) == 0) + return TRUE; + if (card->priv->profile_op != NULL) { + pa_operation_cancel (card->priv->profile_op); + pa_operation_unref (card->priv->profile_op); + card->priv->profile_op = NULL; + } + + if (card->priv->profile != NULL) { + g_free (card->priv->target_profile); + card->priv->target_profile = g_strdup (profile); + + card->priv->profile_op = pa_context_set_card_profile_by_index (card->priv->pa_context, + card->priv->index, + card->priv->target_profile, + _pa_context_set_card_profile_by_index_cb, + card); + + if (card->priv->profile_op == NULL) { + g_warning ("pa_context_set_card_profile_by_index() failed"); + return FALSE; + } + } else { + g_assert (card->priv->human_profile == NULL); + card->priv->profile = g_strdup (profile); + } + + return TRUE; +} + +const GList * +gvc_mixer_card_get_profiles (GvcMixerCard *card) +{ + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), FALSE); + return card->priv->profiles; +} + +static int +sort_profiles (GvcMixerCardProfile *a, + GvcMixerCardProfile *b) +{ + if (a->priority == b->priority) + return 0; + if (a->priority > b->priority) + return 1; + return -1; +} + +gboolean +gvc_mixer_card_set_profiles (GvcMixerCard *card, + GList *profiles) +{ + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), FALSE); + g_return_val_if_fail (card->priv->profiles == NULL, FALSE); + + card->priv->profiles = g_list_sort (profiles, (GCompareFunc) sort_profiles); + + return TRUE; +} + +static void +gvc_mixer_card_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GvcMixerCard *self = GVC_MIXER_CARD (object); + + switch (prop_id) { + case PROP_PA_CONTEXT: + self->priv->pa_context = g_value_get_pointer (value); + break; + case PROP_INDEX: + self->priv->index = g_value_get_ulong (value); + break; + case PROP_ID: + self->priv->id = g_value_get_ulong (value); + break; + case PROP_NAME: + gvc_mixer_card_set_name (self, g_value_get_string (value)); + break; + case PROP_ICON_NAME: + gvc_mixer_card_set_icon_name (self, g_value_get_string (value)); + break; + case PROP_PROFILE: + gvc_mixer_card_set_profile (self, g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gvc_mixer_card_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GvcMixerCard *self = GVC_MIXER_CARD (object); + + switch (prop_id) { + case PROP_PA_CONTEXT: + g_value_set_pointer (value, self->priv->pa_context); + break; + case PROP_INDEX: + g_value_set_ulong (value, self->priv->index); + break; + case PROP_ID: + g_value_set_ulong (value, self->priv->id); + break; + case PROP_NAME: + g_value_set_string (value, self->priv->name); + break; + case PROP_ICON_NAME: + g_value_set_string (value, self->priv->icon_name); + break; + case PROP_PROFILE: + g_value_set_string (value, self->priv->profile); + break; + case PROP_HUMAN_PROFILE: + g_value_set_string (value, self->priv->human_profile); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GObject * +gvc_mixer_card_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GvcMixerCard *self; + + object = G_OBJECT_CLASS (gvc_mixer_card_parent_class)->constructor (type, n_construct_properties, construct_params); + + self = GVC_MIXER_CARD (object); + + self->priv->id = get_next_card_serial (); + + return object; +} + +static void +gvc_mixer_card_class_init (GvcMixerCardClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->constructor = gvc_mixer_card_constructor; + gobject_class->finalize = gvc_mixer_card_finalize; + + gobject_class->set_property = gvc_mixer_card_set_property; + gobject_class->get_property = gvc_mixer_card_get_property; + + g_object_class_install_property (gobject_class, + PROP_INDEX, + g_param_spec_ulong ("index", + "Index", + "The index for this card", + 0, G_MAXULONG, 0, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (gobject_class, + PROP_ID, + g_param_spec_ulong ("id", + "id", + "The id for this card", + 0, G_MAXULONG, 0, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (gobject_class, + PROP_PA_CONTEXT, + g_param_spec_pointer ("pa-context", + "PulseAudio context", + "The PulseAudio context for this card", + G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (gobject_class, + PROP_NAME, + g_param_spec_string ("name", + "Name", + "Name to display for this card", + NULL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_ICON_NAME, + g_param_spec_string ("icon-name", + "Icon Name", + "Name of icon to display for this card", + NULL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_PROFILE, + g_param_spec_string ("profile", + "Profile", + "Name of current profile for this card", + NULL, + G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, + PROP_HUMAN_PROFILE, + g_param_spec_string ("human-profile", + "Profile (Human readable)", + "Name of current profile for this card in human readable form", + NULL, + G_PARAM_READABLE)); + + g_type_class_add_private (klass, sizeof (GvcMixerCardPrivate)); +} + +static void +gvc_mixer_card_init (GvcMixerCard *card) +{ + card->priv = GVC_MIXER_CARD_GET_PRIVATE (card); +} + +GvcMixerCard * +gvc_mixer_card_new (pa_context *context, + guint index) +{ + GObject *object; + + object = g_object_new (GVC_TYPE_MIXER_CARD, + "index", index, + "pa-context", context, + NULL); + return GVC_MIXER_CARD (object); +} + +static void +free_profile (GvcMixerCardProfile *p) +{ + g_free (p->profile); + g_free (p->human_profile); + g_free (p->status); + g_free (p); +} + +static void +gvc_mixer_card_finalize (GObject *object) +{ + GvcMixerCard *mixer_card; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_CARD (object)); + + mixer_card = GVC_MIXER_CARD (object); + + g_return_if_fail (mixer_card->priv != NULL); + + g_free (mixer_card->priv->name); + mixer_card->priv->name = NULL; + + g_free (mixer_card->priv->icon_name); + mixer_card->priv->icon_name = NULL; + + g_free (mixer_card->priv->target_profile); + mixer_card->priv->target_profile = NULL; + + g_free (mixer_card->priv->profile); + mixer_card->priv->profile = NULL; + + g_free (mixer_card->priv->human_profile); + mixer_card->priv->human_profile = NULL; + + g_list_foreach (mixer_card->priv->profiles, (GFunc) free_profile, NULL); + g_list_free (mixer_card->priv->profiles); + mixer_card->priv->profiles = NULL; + + G_OBJECT_CLASS (gvc_mixer_card_parent_class)->finalize (object); +} + diff --git a/src/gvc/gvc-mixer-card.h b/src/gvc/gvc-mixer-card.h new file mode 100644 index 000000000..5a3a7bc45 --- /dev/null +++ b/src/gvc/gvc-mixer-card.h @@ -0,0 +1,83 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008-2009 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __GVC_MIXER_CARD_H +#define __GVC_MIXER_CARD_H + +#include + +G_BEGIN_DECLS + +#define GVC_TYPE_MIXER_CARD (gvc_mixer_card_get_type ()) +#define GVC_MIXER_CARD(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_CARD, GvcMixerCard)) +#define GVC_MIXER_CARD_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_CARD, GvcMixerCardClass)) +#define GVC_IS_MIXER_CARD(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_CARD)) +#define GVC_IS_MIXER_CARD_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_CARD)) +#define GVC_MIXER_CARD_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_CARD, GvcMixerCardClass)) + +typedef struct GvcMixerCardPrivate GvcMixerCardPrivate; + +typedef struct +{ + GObject parent; + GvcMixerCardPrivate *priv; +} GvcMixerCard; + +typedef struct +{ + GObjectClass parent_class; + + /* vtable */ +} GvcMixerCardClass; + +typedef struct +{ + char *profile; + char *human_profile; + char *status; + guint priority; + guint n_sinks, n_sources; +} GvcMixerCardProfile; + +GType gvc_mixer_card_get_type (void); + +guint gvc_mixer_card_get_id (GvcMixerCard *card); +guint gvc_mixer_card_get_index (GvcMixerCard *card); +const char * gvc_mixer_card_get_name (GvcMixerCard *card); +const char * gvc_mixer_card_get_icon_name (GvcMixerCard *card); +GvcMixerCardProfile * gvc_mixer_card_get_profile (GvcMixerCard *card); +const GList * gvc_mixer_card_get_profiles (GvcMixerCard *card); + +gboolean gvc_mixer_card_change_profile (GvcMixerCard *card, + const char *profile); + +/* private */ +gboolean gvc_mixer_card_set_name (GvcMixerCard *card, + const char *name); +gboolean gvc_mixer_card_set_icon_name (GvcMixerCard *card, + const char *name); +gboolean gvc_mixer_card_set_profile (GvcMixerCard *card, + const char *profile); +gboolean gvc_mixer_card_set_profiles (GvcMixerCard *card, + GList *profiles); + +G_END_DECLS + +#endif /* __GVC_MIXER_CARD_H */ diff --git a/src/gvc/gvc-mixer-control-private.h b/src/gvc/gvc-mixer-control-private.h new file mode 100644 index 000000000..ac79975aa --- /dev/null +++ b/src/gvc/gvc-mixer-control-private.h @@ -0,0 +1,35 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __GVC_MIXER_CONTROL_PRIVATE_H +#define __GVC_MIXER_CONTROL_PRIVATE_H + +#include +#include +#include "gvc-mixer-stream.h" +#include "gvc-mixer-card.h" + +G_BEGIN_DECLS + +pa_context * gvc_mixer_control_get_pa_context (GvcMixerControl *control); + +G_END_DECLS + +#endif /* __GVC_MIXER_CONTROL_PRIVATE_H */ diff --git a/src/gvc/gvc-mixer-control.c b/src/gvc/gvc-mixer-control.c new file mode 100644 index 000000000..d8d80f6af --- /dev/null +++ b/src/gvc/gvc-mixer-control.c @@ -0,0 +1,2232 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2006-2008 Lennart Poettering + * Copyright (C) 2008 Sjoerd Simons + * Copyright (C) 2008 William Jon McCann + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include "config.h" + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include "gvc-mixer-control.h" +#include "gvc-mixer-sink.h" +#include "gvc-mixer-source.h" +#include "gvc-mixer-sink-input.h" +#include "gvc-mixer-source-output.h" +#include "gvc-mixer-event-role.h" +#include "gvc-mixer-card.h" +#include "gvc-mixer-card-private.h" +#include "gvc-channel-map-private.h" +#include "gvc-mixer-control-private.h" + +#define GVC_MIXER_CONTROL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_CONTROL, GvcMixerControlPrivate)) + +#define RECONNECT_DELAY 5 + +enum { + PROP_0, + PROP_NAME +}; + +struct GvcMixerControlPrivate +{ + pa_glib_mainloop *pa_mainloop; + pa_mainloop_api *pa_api; + pa_context *pa_context; + int n_outstanding; + guint reconnect_id; + char *name; + + gboolean default_sink_is_set; + guint default_sink_id; + char *default_sink_name; + gboolean default_source_is_set; + guint default_source_id; + char *default_source_name; + + gboolean event_sink_input_is_set; + guint event_sink_input_id; + + GHashTable *all_streams; + GHashTable *sinks; /* fixed outputs */ + GHashTable *sources; /* fixed inputs */ + GHashTable *sink_inputs; /* routable output streams */ + GHashTable *source_outputs; /* routable input streams */ + GHashTable *clients; + GHashTable *cards; + + GvcMixerStream *new_default_stream; /* new default stream, used in gvc_mixer_control_set_default_sink () */ +}; + +enum { + CONNECTING, + READY, + STREAM_ADDED, + STREAM_REMOVED, + CARD_ADDED, + CARD_REMOVED, + DEFAULT_SINK_CHANGED, + DEFAULT_SOURCE_CHANGED, + LAST_SIGNAL +}; + +static guint signals [LAST_SIGNAL] = { 0, }; + +static void gvc_mixer_control_class_init (GvcMixerControlClass *klass); +static void gvc_mixer_control_init (GvcMixerControl *mixer_control); +static void gvc_mixer_control_finalize (GObject *object); + +G_DEFINE_TYPE (GvcMixerControl, gvc_mixer_control, G_TYPE_OBJECT) + +pa_context * +gvc_mixer_control_get_pa_context (GvcMixerControl *control) +{ + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + return control->priv->pa_context; +} + +/** + * gvc_mixer_control_get_event_sink_input: + * + * @control: + * + * Returns: (transfer none): + */ +GvcMixerStream * +gvc_mixer_control_get_event_sink_input (GvcMixerControl *control) +{ + GvcMixerStream *stream; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + stream = g_hash_table_lookup (control->priv->all_streams, + GUINT_TO_POINTER (control->priv->event_sink_input_id)); + + return stream; +} + +static void +gvc_mixer_control_stream_restore_cb (pa_context *c, + const pa_ext_stream_restore_info *info, + int eol, + void *userdata) +{ + pa_operation *o; + GvcMixerControl *control = (GvcMixerControl *) userdata; + pa_ext_stream_restore_info new_info; + + if (eol || control->priv->new_default_stream == NULL) + return; + + new_info.name = info->name; + new_info.channel_map = info->channel_map; + new_info.volume = info->volume; + new_info.mute = info->mute; + + new_info.device = gvc_mixer_stream_get_name (control->priv->new_default_stream); + + o = pa_ext_stream_restore_write (control->priv->pa_context, + PA_UPDATE_REPLACE, + &new_info, 1, + TRUE, NULL, NULL); + + if (o == NULL) { + g_warning ("pa_ext_stream_restore_write() failed: %s", + pa_strerror (pa_context_errno (control->priv->pa_context))); + return; + } + + g_debug ("Changed default device for %s to %s", info->name, info->device); + + pa_operation_unref (o); +} + +gboolean +gvc_mixer_control_set_default_sink (GvcMixerControl *control, + GvcMixerStream *stream) +{ + pa_operation *o; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE); + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + o = pa_context_set_default_sink (control->priv->pa_context, + gvc_mixer_stream_get_name (stream), + NULL, + NULL); + if (o == NULL) { + g_warning ("pa_context_set_default_sink() failed: %s", + pa_strerror (pa_context_errno (control->priv->pa_context))); + return FALSE; + } + + pa_operation_unref (o); + + control->priv->new_default_stream = stream; + g_object_add_weak_pointer (G_OBJECT (stream), (gpointer *) &control->priv->new_default_stream); + + o = pa_ext_stream_restore_read (control->priv->pa_context, + gvc_mixer_control_stream_restore_cb, + control); + + if (o == NULL) { + g_warning ("pa_ext_stream_restore_read() failed: %s", + pa_strerror (pa_context_errno (control->priv->pa_context))); + return FALSE; + } + + pa_operation_unref (o); + + return TRUE; +} + +gboolean +gvc_mixer_control_set_default_source (GvcMixerControl *control, + GvcMixerStream *stream) +{ + pa_operation *o; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE); + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + o = pa_context_set_default_source (control->priv->pa_context, + gvc_mixer_stream_get_name (stream), + NULL, + NULL); + if (o == NULL) { + g_warning ("pa_context_set_default_source() failed"); + return FALSE; + } + + pa_operation_unref (o); + + return TRUE; +} + +/** + * gvc_mixer_control_get_default_sink: + * + * @control: + * + * Returns: (transfer none): + */ +GvcMixerStream * +gvc_mixer_control_get_default_sink (GvcMixerControl *control) +{ + GvcMixerStream *stream; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + if (control->priv->default_sink_is_set) { + stream = g_hash_table_lookup (control->priv->all_streams, + GUINT_TO_POINTER (control->priv->default_sink_id)); + } else { + stream = NULL; + } + + return stream; +} + +/** + * gvc_mixer_control_get_default_source: + * + * @control: + * + * Returns: (transfer none): + */ +GvcMixerStream * +gvc_mixer_control_get_default_source (GvcMixerControl *control) +{ + GvcMixerStream *stream; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + if (control->priv->default_source_is_set) { + stream = g_hash_table_lookup (control->priv->all_streams, + GUINT_TO_POINTER (control->priv->default_source_id)); + } else { + stream = NULL; + } + + return stream; +} + +static gpointer +gvc_mixer_control_lookup_id (GHashTable *hash_table, + guint id) +{ + return g_hash_table_lookup (hash_table, + GUINT_TO_POINTER (id)); +} + +/** + * gvc_mixer_control_lookup_stream_id: + * + * @control: + * @id: + * + * Returns: (transfer none): + */ +GvcMixerStream * +gvc_mixer_control_lookup_stream_id (GvcMixerControl *control, + guint id) +{ + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + return gvc_mixer_control_lookup_id (control->priv->all_streams, id); +} + +/** + * gvc_mixer_control_lookup_card_id: + * + * @control: + * @id: + * + * Returns: (transfer none): + */ +GvcMixerCard * +gvc_mixer_control_lookup_card_id (GvcMixerControl *control, + guint id) +{ + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + return gvc_mixer_control_lookup_id (control->priv->cards, id); +} + +static void +listify_hash_values_hfunc (gpointer key, + gpointer value, + gpointer user_data) +{ + GSList **list = user_data; + + *list = g_slist_prepend (*list, value); +} + +static int +gvc_name_collate (const char *namea, + const char *nameb) +{ + if (nameb == NULL && namea == NULL) + return 0; + if (nameb == NULL) + return 1; + if (namea == NULL) + return -1; + + return g_utf8_collate (namea, nameb); +} + +static int +gvc_card_collate (GvcMixerCard *a, + GvcMixerCard *b) +{ + const char *namea; + const char *nameb; + + g_return_val_if_fail (a == NULL || GVC_IS_MIXER_CARD (a), 0); + g_return_val_if_fail (b == NULL || GVC_IS_MIXER_CARD (b), 0); + + namea = gvc_mixer_card_get_name (a); + nameb = gvc_mixer_card_get_name (b); + + return gvc_name_collate (namea, nameb); +} + +/** + * gvc_mixer_control_get_cards: + * + * @control: + * + * Returns: (transfer container) (element-type Gvc.MixerCard): + */ +GSList * +gvc_mixer_control_get_cards (GvcMixerControl *control) +{ + GSList *retval; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + retval = NULL; + g_hash_table_foreach (control->priv->cards, + listify_hash_values_hfunc, + &retval); + return g_slist_sort (retval, (GCompareFunc) gvc_card_collate); +} + +static int +gvc_stream_collate (GvcMixerStream *a, + GvcMixerStream *b) +{ + const char *namea; + const char *nameb; + + g_return_val_if_fail (a == NULL || GVC_IS_MIXER_STREAM (a), 0); + g_return_val_if_fail (b == NULL || GVC_IS_MIXER_STREAM (b), 0); + + namea = gvc_mixer_stream_get_name (a); + nameb = gvc_mixer_stream_get_name (b); + + return gvc_name_collate (namea, nameb); +} + +/** + * gvc_mixer_control_get_streams: + * + * @control: + * + * Returns: (transfer container) (element-type Gvc.MixerStream): + */ +GSList * +gvc_mixer_control_get_streams (GvcMixerControl *control) +{ + GSList *retval; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + retval = NULL; + g_hash_table_foreach (control->priv->all_streams, + listify_hash_values_hfunc, + &retval); + return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate); +} + +/** + * gvc_mixer_control_get_sinks: + * + * @control: + * + * Returns: (transfer container) (element-type Gvc.MixerSink): + */ +GSList * +gvc_mixer_control_get_sinks (GvcMixerControl *control) +{ + GSList *retval; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + retval = NULL; + g_hash_table_foreach (control->priv->sinks, + listify_hash_values_hfunc, + &retval); + return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate); +} + +/** + * gvc_mixer_control_get_sources: + * + * @control: + * + * Returns: (transfer container) (element-type Gvc.MixerSource): + */ +GSList * +gvc_mixer_control_get_sources (GvcMixerControl *control) +{ + GSList *retval; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + retval = NULL; + g_hash_table_foreach (control->priv->sources, + listify_hash_values_hfunc, + &retval); + return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate); +} + +/** + * gvc_mixer_control_get_sink_inputs: + * + * @control: + * + * Returns: (transfer container) (element-type Gvc.MixerSinkInput): + */ +GSList * +gvc_mixer_control_get_sink_inputs (GvcMixerControl *control) +{ + GSList *retval; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + retval = NULL; + g_hash_table_foreach (control->priv->sink_inputs, + listify_hash_values_hfunc, + &retval); + return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate); +} + +/** + * gvc_mixer_control_get_source_outputs: + * + * @control: + * + * Returns: (transfer container) (element-type Gvc.MixerSourceOutput): + */ +GSList * +gvc_mixer_control_get_source_outputs (GvcMixerControl *control) +{ + GSList *retval; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + retval = NULL; + g_hash_table_foreach (control->priv->source_outputs, + listify_hash_values_hfunc, + &retval); + return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate); +} + +static void +dec_outstanding (GvcMixerControl *control) +{ + if (control->priv->n_outstanding <= 0) { + return; + } + + if (--control->priv->n_outstanding <= 0) { + g_signal_emit (G_OBJECT (control), signals[READY], 0); + } +} + +gboolean +gvc_mixer_control_is_ready (GvcMixerControl *control) +{ + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE); + + return (control->priv->n_outstanding == 0); +} + + +static void +_set_default_source (GvcMixerControl *control, + GvcMixerStream *stream) +{ + guint new_id; + + if (stream == NULL) { + control->priv->default_source_id = 0; + control->priv->default_source_is_set = FALSE; + g_signal_emit (control, + signals[DEFAULT_SOURCE_CHANGED], + 0, + PA_INVALID_INDEX); + return; + } + + new_id = gvc_mixer_stream_get_id (stream); + + if (control->priv->default_source_id != new_id) { + control->priv->default_source_id = new_id; + control->priv->default_source_is_set = TRUE; + g_signal_emit (control, + signals[DEFAULT_SOURCE_CHANGED], + 0, + new_id); + } +} + +static void +_set_default_sink (GvcMixerControl *control, + GvcMixerStream *stream) +{ + guint new_id; + + if (stream == NULL) { + /* Don't tell front-ends about an unset default + * sink if it's already unset */ + if (control->priv->default_sink_is_set == FALSE) + return; + control->priv->default_sink_id = 0; + control->priv->default_sink_is_set = FALSE; + g_signal_emit (control, + signals[DEFAULT_SINK_CHANGED], + 0, + PA_INVALID_INDEX); + return; + } + + new_id = gvc_mixer_stream_get_id (stream); + + if (control->priv->default_sink_id != new_id) { + control->priv->default_sink_id = new_id; + control->priv->default_sink_is_set = TRUE; + g_signal_emit (control, + signals[DEFAULT_SINK_CHANGED], + 0, + new_id); + } +} + +static gboolean +_stream_has_name (gpointer key, + GvcMixerStream *stream, + const char *name) +{ + const char *t_name; + + t_name = gvc_mixer_stream_get_name (stream); + + if (t_name != NULL + && name != NULL + && strcmp (t_name, name) == 0) { + return TRUE; + } + + return FALSE; +} + +static GvcMixerStream * +find_stream_for_name (GvcMixerControl *control, + const char *name) +{ + GvcMixerStream *stream; + + stream = g_hash_table_find (control->priv->all_streams, + (GHRFunc)_stream_has_name, + (char *)name); + return stream; +} + +static void +update_default_source_from_name (GvcMixerControl *control, + const char *name) +{ + gboolean changed; + + if ((control->priv->default_source_name == NULL + && name != NULL) + || (control->priv->default_source_name != NULL + && name == NULL) + || strcmp (control->priv->default_source_name, name) != 0) { + changed = TRUE; + } + + if (changed) { + GvcMixerStream *stream; + + g_free (control->priv->default_source_name); + control->priv->default_source_name = g_strdup (name); + + stream = find_stream_for_name (control, name); + _set_default_source (control, stream); + } +} + +static void +update_default_sink_from_name (GvcMixerControl *control, + const char *name) +{ + gboolean changed; + + if ((control->priv->default_sink_name == NULL + && name != NULL) + || (control->priv->default_sink_name != NULL + && name == NULL) + || strcmp (control->priv->default_sink_name, name) != 0) { + changed = TRUE; + } + + if (changed) { + GvcMixerStream *stream; + g_free (control->priv->default_sink_name); + control->priv->default_sink_name = g_strdup (name); + + stream = find_stream_for_name (control, name); + _set_default_sink (control, stream); + } +} + +static void +update_server (GvcMixerControl *control, + const pa_server_info *info) +{ + if (info->default_source_name != NULL) { + update_default_source_from_name (control, info->default_source_name); + } + if (info->default_sink_name != NULL) { + update_default_sink_from_name (control, info->default_sink_name); + } +} + +static void +remove_stream (GvcMixerControl *control, + GvcMixerStream *stream) +{ + guint id; + + g_object_ref (stream); + + id = gvc_mixer_stream_get_id (stream); + + if (id == control->priv->default_sink_id) { + _set_default_sink (control, NULL); + } else if (id == control->priv->default_source_id) { + _set_default_source (control, NULL); + } + + g_hash_table_remove (control->priv->all_streams, + GUINT_TO_POINTER (id)); + g_signal_emit (G_OBJECT (control), + signals[STREAM_REMOVED], + 0, + gvc_mixer_stream_get_id (stream)); + g_object_unref (stream); +} + +static void +add_stream (GvcMixerControl *control, + GvcMixerStream *stream) +{ + g_hash_table_insert (control->priv->all_streams, + GUINT_TO_POINTER (gvc_mixer_stream_get_id (stream)), + stream); + g_signal_emit (G_OBJECT (control), + signals[STREAM_ADDED], + 0, + gvc_mixer_stream_get_id (stream)); +} + +static void +update_sink (GvcMixerControl *control, + const pa_sink_info *info) +{ + GvcMixerStream *stream; + gboolean is_new; + pa_volume_t max_volume; + GvcChannelMap *map; + char map_buff[PA_CHANNEL_MAP_SNPRINT_MAX]; + + pa_channel_map_snprint (map_buff, PA_CHANNEL_MAP_SNPRINT_MAX, &info->channel_map); +#if 1 + g_debug ("Updating sink: index=%u name='%s' description='%s' map='%s'", + info->index, + info->name, + info->description, + map_buff); +#endif + + map = NULL; + is_new = FALSE; + stream = g_hash_table_lookup (control->priv->sinks, + GUINT_TO_POINTER (info->index)); + if (stream == NULL) { +#if PA_MICRO > 15 + GList *list = NULL; + guint i; +#endif /* PA_MICRO > 15 */ + + map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map); + stream = gvc_mixer_sink_new (control->priv->pa_context, + info->index, + map); +#if PA_MICRO > 15 + for (i = 0; i < info->n_ports; i++) { + GvcMixerStreamPort *port; + + port = g_new0 (GvcMixerStreamPort, 1); + port->port = g_strdup (info->ports[i]->name); + port->human_port = g_strdup (info->ports[i]->description); + port->priority = info->ports[i]->priority; + list = g_list_prepend (list, port); + } + gvc_mixer_stream_set_ports (stream, list); +#endif /* PA_MICRO > 15 */ + g_object_unref (map); + is_new = TRUE; + } else if (gvc_mixer_stream_is_running (stream)) { + /* Ignore events if volume changes are outstanding */ + g_debug ("Ignoring event, volume changes are outstanding"); + return; + } + + max_volume = pa_cvolume_max (&info->volume); + gvc_mixer_stream_set_name (stream, info->name); + gvc_mixer_stream_set_card_index (stream, info->card); + gvc_mixer_stream_set_description (stream, info->description); + gvc_mixer_stream_set_icon_name (stream, "audio-card"); + gvc_mixer_stream_set_volume (stream, (guint)max_volume); + gvc_mixer_stream_set_is_muted (stream, info->mute); + gvc_mixer_stream_set_can_decibel (stream, !!(info->flags & PA_SINK_DECIBEL_VOLUME)); + gvc_mixer_stream_set_base_volume (stream, (guint32) info->base_volume); +#if PA_MICRO > 15 + if (info->active_port != NULL) + gvc_mixer_stream_set_port (stream, info->active_port->name); +#endif /* PA_MICRO > 15 */ + + if (is_new) { + g_hash_table_insert (control->priv->sinks, + GUINT_TO_POINTER (info->index), + g_object_ref (stream)); + add_stream (control, stream); + } + + if (control->priv->default_sink_name != NULL + && info->name != NULL + && strcmp (control->priv->default_sink_name, info->name) == 0) { + _set_default_sink (control, stream); + } + + if (map == NULL) + map = (GvcChannelMap *) gvc_mixer_stream_get_channel_map (stream); + gvc_channel_map_volume_changed (map, &info->volume, FALSE); +} + +static void +update_source (GvcMixerControl *control, + const pa_source_info *info) +{ + GvcMixerStream *stream; + gboolean is_new; + pa_volume_t max_volume; + +#if 1 + g_debug ("Updating source: index=%u name='%s' description='%s'", + info->index, + info->name, + info->description); +#endif + + /* completely ignore monitors, they're not real sources */ + if (info->monitor_of_sink != PA_INVALID_INDEX) { + return; + } + + is_new = FALSE; + + stream = g_hash_table_lookup (control->priv->sources, + GUINT_TO_POINTER (info->index)); + if (stream == NULL) { +#if PA_MICRO > 15 + GList *list = NULL; + guint i; +#endif /* PA_MICRO > 15 */ + GvcChannelMap *map; + + map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map); + stream = gvc_mixer_source_new (control->priv->pa_context, + info->index, + map); +#if PA_MICRO > 15 + for (i = 0; i < info->n_ports; i++) { + GvcMixerStreamPort *port; + + port = g_new0 (GvcMixerStreamPort, 1); + port->port = g_strdup (info->ports[i]->name); + port->human_port = g_strdup (info->ports[i]->description); + port->priority = info->ports[i]->priority; + list = g_list_prepend (list, port); + } + gvc_mixer_stream_set_ports (stream, list); +#endif /* PA_MICRO > 15 */ + + g_object_unref (map); + is_new = TRUE; + } else if (gvc_mixer_stream_is_running (stream)) { + /* Ignore events if volume changes are outstanding */ + g_debug ("Ignoring event, volume changes are outstanding"); + return; + } + + max_volume = pa_cvolume_max (&info->volume); + + gvc_mixer_stream_set_name (stream, info->name); + gvc_mixer_stream_set_card_index (stream, info->card); + gvc_mixer_stream_set_description (stream, info->description); + gvc_mixer_stream_set_icon_name (stream, "audio-input-microphone"); + gvc_mixer_stream_set_volume (stream, (guint)max_volume); + gvc_mixer_stream_set_is_muted (stream, info->mute); + gvc_mixer_stream_set_can_decibel (stream, !!(info->flags & PA_SOURCE_DECIBEL_VOLUME)); + gvc_mixer_stream_set_base_volume (stream, (guint32) info->base_volume); +#if PA_MICRO > 15 + if (info->active_port != NULL) + gvc_mixer_stream_set_port (stream, info->active_port->name); +#endif /* PA_MICRO > 15 */ + + if (is_new) { + g_hash_table_insert (control->priv->sources, + GUINT_TO_POINTER (info->index), + g_object_ref (stream)); + add_stream (control, stream); + } + + if (control->priv->default_source_name != NULL + && info->name != NULL + && strcmp (control->priv->default_source_name, info->name) == 0) { + _set_default_source (control, stream); + } +} + +static void +set_icon_name_from_proplist (GvcMixerStream *stream, + pa_proplist *l, + const char *default_icon_name) +{ + const char *t; + + if ((t = pa_proplist_gets (l, PA_PROP_MEDIA_ICON_NAME))) { + goto finish; + } + + if ((t = pa_proplist_gets (l, PA_PROP_WINDOW_ICON_NAME))) { + goto finish; + } + + if ((t = pa_proplist_gets (l, PA_PROP_APPLICATION_ICON_NAME))) { + goto finish; + } + + if ((t = pa_proplist_gets (l, PA_PROP_MEDIA_ROLE))) { + + if (strcmp (t, "video") == 0 || + strcmp (t, "phone") == 0) { + goto finish; + } + + if (strcmp (t, "music") == 0) { + t = "audio"; + goto finish; + } + + if (strcmp (t, "game") == 0) { + t = "applications-games"; + goto finish; + } + + if (strcmp (t, "event") == 0) { + t = "dialog-information"; + goto finish; + } + } + + t = default_icon_name; + + finish: + gvc_mixer_stream_set_icon_name (stream, t); +} + +static void +set_is_event_stream_from_proplist (GvcMixerStream *stream, + pa_proplist *l) +{ + const char *t; + gboolean is_event_stream; + + is_event_stream = FALSE; + + if ((t = pa_proplist_gets (l, PA_PROP_MEDIA_ROLE))) { + if (g_str_equal (t, "event")) + is_event_stream = TRUE; + } + + gvc_mixer_stream_set_is_event_stream (stream, is_event_stream); +} + +static void +set_application_id_from_proplist (GvcMixerStream *stream, + pa_proplist *l) +{ + const char *t; + + if ((t = pa_proplist_gets (l, PA_PROP_APPLICATION_ID))) { + gvc_mixer_stream_set_application_id (stream, t); + } +} + +static void +update_sink_input (GvcMixerControl *control, + const pa_sink_input_info *info) +{ + GvcMixerStream *stream; + gboolean is_new; + pa_volume_t max_volume; + const char *name; + +#if 0 + g_debug ("Updating sink input: index=%u name='%s' client=%u sink=%u", + info->index, + info->name, + info->client, + info->sink); +#endif + + is_new = FALSE; + + stream = g_hash_table_lookup (control->priv->sink_inputs, + GUINT_TO_POINTER (info->index)); + if (stream == NULL) { + GvcChannelMap *map; + map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map); + stream = gvc_mixer_sink_input_new (control->priv->pa_context, + info->index, + map); + g_object_unref (map); + is_new = TRUE; + } else if (gvc_mixer_stream_is_running (stream)) { + /* Ignore events if volume changes are outstanding */ + g_debug ("Ignoring event, volume changes are outstanding"); + return; + } + + max_volume = pa_cvolume_max (&info->volume); + + name = (const char *)g_hash_table_lookup (control->priv->clients, + GUINT_TO_POINTER (info->client)); + gvc_mixer_stream_set_name (stream, name); + gvc_mixer_stream_set_description (stream, info->name); + + set_application_id_from_proplist (stream, info->proplist); + set_is_event_stream_from_proplist (stream, info->proplist); + set_icon_name_from_proplist (stream, info->proplist, "applications-multimedia"); + gvc_mixer_stream_set_volume (stream, (guint)max_volume); + gvc_mixer_stream_set_is_muted (stream, info->mute); + gvc_mixer_stream_set_is_virtual (stream, info->client == PA_INVALID_INDEX); + + if (is_new) { + g_hash_table_insert (control->priv->sink_inputs, + GUINT_TO_POINTER (info->index), + g_object_ref (stream)); + add_stream (control, stream); + } +} + +static void +update_source_output (GvcMixerControl *control, + const pa_source_output_info *info) +{ + GvcMixerStream *stream; + gboolean is_new; + const char *name; + +#if 1 + g_debug ("Updating source output: index=%u name='%s' client=%u source=%u", + info->index, + info->name, + info->client, + info->source); +#endif + + is_new = FALSE; + stream = g_hash_table_lookup (control->priv->source_outputs, + GUINT_TO_POINTER (info->index)); + if (stream == NULL) { + GvcChannelMap *map; + map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map); + stream = gvc_mixer_source_output_new (control->priv->pa_context, + info->index, + map); + g_object_unref (map); + is_new = TRUE; + } + + name = (const char *)g_hash_table_lookup (control->priv->clients, + GUINT_TO_POINTER (info->client)); + + gvc_mixer_stream_set_name (stream, name); + gvc_mixer_stream_set_description (stream, info->name); + set_application_id_from_proplist (stream, info->proplist); + set_is_event_stream_from_proplist (stream, info->proplist); + set_icon_name_from_proplist (stream, info->proplist, "audio-input-microphone"); + + if (is_new) { + g_hash_table_insert (control->priv->source_outputs, + GUINT_TO_POINTER (info->index), + g_object_ref (stream)); + add_stream (control, stream); + } +} + +static void +update_client (GvcMixerControl *control, + const pa_client_info *info) +{ +#if 1 + g_debug ("Updating client: index=%u name='%s'", + info->index, + info->name); +#endif + g_hash_table_insert (control->priv->clients, + GUINT_TO_POINTER (info->index), + g_strdup (info->name)); +} + +static char * +card_num_streams_to_status (guint sinks, + guint sources) +{ + char *sinks_str; + char *sources_str; + char *ret; + + if (sinks == 0 && sources == 0) { + /* translators: + * The device has been disabled */ + return g_strdup (_("Disabled")); + } + if (sinks == 0) { + sinks_str = NULL; + } else { + /* translators: + * The number of sound outputs on a particular device */ + sinks_str = g_strdup_printf (ngettext ("%u Output", + "%u Outputs", + sinks), + sinks); + } + if (sources == 0) { + sources_str = NULL; + } else { + /* translators: + * The number of sound inputs on a particular device */ + sources_str = g_strdup_printf (ngettext ("%u Input", + "%u Inputs", + sources), + sources); + } + if (sources_str == NULL) + return sinks_str; + if (sinks_str == NULL) + return sources_str; + ret = g_strdup_printf ("%s / %s", sinks_str, sources_str); + g_free (sinks_str); + g_free (sources_str); + return ret; +} + +static void +update_card (GvcMixerControl *control, + const pa_card_info *info) +{ + GvcMixerCard *card; + gboolean is_new; +#if 1 + guint i; + const char *key; + void *state; + + g_debug ("Udpating card %s (index: %u driver: %s):", + info->name, info->index, info->driver); + + for (i = 0; i < info->n_profiles; i++) { + struct pa_card_profile_info pi = info->profiles[i]; + gboolean is_default; + + is_default = (g_strcmp0 (pi.name, info->active_profile->name) == 0); + g_debug ("\tProfile '%s': %d sources %d sinks%s", + pi.name, pi.n_sources, pi.n_sinks, + is_default ? " (Current)" : ""); + } + state = NULL; + key = pa_proplist_iterate (info->proplist, &state); + while (key != NULL) { + g_debug ("\tProperty: '%s' = '%s'", + key, pa_proplist_gets (info->proplist, key)); + key = pa_proplist_iterate (info->proplist, &state); + } +#endif + card = g_hash_table_lookup (control->priv->cards, + GUINT_TO_POINTER (info->index)); + if (card == NULL) { + GList *list = NULL; + + for (i = 0; i < info->n_profiles; i++) { + struct pa_card_profile_info pi = info->profiles[i]; + GvcMixerCardProfile *profile; + + profile = g_new0 (GvcMixerCardProfile, 1); + profile->profile = g_strdup (pi.name); + profile->human_profile = g_strdup (pi.description); + profile->status = card_num_streams_to_status (pi.n_sinks, pi.n_sources); + profile->n_sinks = pi.n_sinks; + profile->n_sources = pi.n_sources; + profile->priority = pi.priority; + list = g_list_prepend (list, profile); + } + card = gvc_mixer_card_new (control->priv->pa_context, + info->index); + gvc_mixer_card_set_profiles (card, list); + is_new = TRUE; + } + + gvc_mixer_card_set_name (card, pa_proplist_gets (info->proplist, "device.description")); + gvc_mixer_card_set_icon_name (card, pa_proplist_gets (info->proplist, "device.icon_name")); + gvc_mixer_card_set_profile (card, info->active_profile->name); + + if (is_new) { + g_hash_table_insert (control->priv->cards, + GUINT_TO_POINTER (info->index), + g_object_ref (card)); + } + g_signal_emit (G_OBJECT (control), + signals[CARD_ADDED], + 0, + info->index); +} + +static void +_pa_context_get_sink_info_cb (pa_context *context, + const pa_sink_info *i, + int eol, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + + if (eol < 0) { + if (pa_context_errno (context) == PA_ERR_NOENTITY) { + return; + } + + g_warning ("Sink callback failure"); + return; + } + + if (eol > 0) { + dec_outstanding (control); + return; + } + + update_sink (control, i); +} + +static void +_pa_context_get_source_info_cb (pa_context *context, + const pa_source_info *i, + int eol, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + + if (eol < 0) { + if (pa_context_errno (context) == PA_ERR_NOENTITY) { + return; + } + + g_warning ("Source callback failure"); + return; + } + + if (eol > 0) { + dec_outstanding (control); + return; + } + + update_source (control, i); +} + +static void +_pa_context_get_sink_input_info_cb (pa_context *context, + const pa_sink_input_info *i, + int eol, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + + if (eol < 0) { + if (pa_context_errno (context) == PA_ERR_NOENTITY) { + return; + } + + g_warning ("Sink input callback failure"); + return; + } + + if (eol > 0) { + dec_outstanding (control); + return; + } + + update_sink_input (control, i); +} + +static void +_pa_context_get_source_output_info_cb (pa_context *context, + const pa_source_output_info *i, + int eol, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + + if (eol < 0) { + if (pa_context_errno (context) == PA_ERR_NOENTITY) { + return; + } + + g_warning ("Source output callback failure"); + return; + } + + if (eol > 0) { + dec_outstanding (control); + return; + } + + update_source_output (control, i); +} + +static void +_pa_context_get_client_info_cb (pa_context *context, + const pa_client_info *i, + int eol, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + + if (eol < 0) { + if (pa_context_errno (context) == PA_ERR_NOENTITY) { + return; + } + + g_warning ("Client callback failure"); + return; + } + + if (eol > 0) { + dec_outstanding (control); + return; + } + + update_client (control, i); +} + +static void +_pa_context_get_card_info_by_index_cb (pa_context *context, + const pa_card_info *i, + int eol, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + + if (eol < 0) { + if (pa_context_errno (context) == PA_ERR_NOENTITY) + return; + + g_warning ("Card callback failure"); + return; + } + + if (eol > 0) { + dec_outstanding (control); + return; + } + + update_card (control, i); +} + +static void +_pa_context_get_server_info_cb (pa_context *context, + const pa_server_info *i, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + + if (i == NULL) { + g_warning ("Server info callback failure"); + return; + } + + update_server (control, i); + dec_outstanding (control); +} + +static void +remove_event_role_stream (GvcMixerControl *control) +{ + g_debug ("Removing event role"); +} + +static void +update_event_role_stream (GvcMixerControl *control, + const pa_ext_stream_restore_info *info) +{ + GvcMixerStream *stream; + gboolean is_new; + pa_volume_t max_volume; + + if (strcmp (info->name, "sink-input-by-media-role:event") != 0) { + return; + } + +#if 0 + g_debug ("Updating event role: name='%s' device='%s'", + info->name, + info->device); +#endif + + is_new = FALSE; + + if (!control->priv->event_sink_input_is_set) { + pa_channel_map pa_map; + GvcChannelMap *map; + + pa_map.channels = 1; + pa_map.map[0] = PA_CHANNEL_POSITION_MONO; + map = gvc_channel_map_new_from_pa_channel_map (&pa_map); + + stream = gvc_mixer_event_role_new (control->priv->pa_context, + info->device, + map); + control->priv->event_sink_input_id = gvc_mixer_stream_get_id (stream); + control->priv->event_sink_input_is_set = TRUE; + + is_new = TRUE; + } else { + stream = g_hash_table_lookup (control->priv->all_streams, + GUINT_TO_POINTER (control->priv->event_sink_input_id)); + } + + max_volume = pa_cvolume_max (&info->volume); + + gvc_mixer_stream_set_name (stream, _("System Sounds")); + gvc_mixer_stream_set_icon_name (stream, "multimedia-volume-control"); + gvc_mixer_stream_set_volume (stream, (guint)max_volume); + gvc_mixer_stream_set_is_muted (stream, info->mute); + + if (is_new) { + add_stream (control, stream); + } +} + +static void +_pa_ext_stream_restore_read_cb (pa_context *context, + const pa_ext_stream_restore_info *i, + int eol, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + + if (eol < 0) { + g_debug ("Failed to initialized stream_restore extension: %s", + pa_strerror (pa_context_errno (context))); + remove_event_role_stream (control); + return; + } + + if (eol > 0) { + dec_outstanding (control); + /* If we don't have an event stream to restore, then + * set one up with a default 100% volume */ + if (!control->priv->event_sink_input_is_set) { + pa_ext_stream_restore_info info; + + memset (&info, 0, sizeof(info)); + info.name = "sink-input-by-media-role:event"; + info.volume.channels = 1; + info.volume.values[0] = PA_VOLUME_NORM; + update_event_role_stream (control, &info); + } + return; + } + + update_event_role_stream (control, i); +} + +static void +_pa_ext_stream_restore_subscribe_cb (pa_context *context, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + pa_operation *o; + + o = pa_ext_stream_restore_read (context, + _pa_ext_stream_restore_read_cb, + control); + if (o == NULL) { + g_warning ("pa_ext_stream_restore_read() failed"); + return; + } + + pa_operation_unref (o); +} + +static void +req_update_server_info (GvcMixerControl *control, + int index) +{ + pa_operation *o; + + o = pa_context_get_server_info (control->priv->pa_context, + _pa_context_get_server_info_cb, + control); + if (o == NULL) { + g_warning ("pa_context_get_server_info() failed"); + return; + } + pa_operation_unref (o); +} + +static void +req_update_client_info (GvcMixerControl *control, + int index) +{ + pa_operation *o; + + if (index < 0) { + o = pa_context_get_client_info_list (control->priv->pa_context, + _pa_context_get_client_info_cb, + control); + } else { + o = pa_context_get_client_info (control->priv->pa_context, + index, + _pa_context_get_client_info_cb, + control); + } + + if (o == NULL) { + g_warning ("pa_context_client_info_list() failed"); + return; + } + pa_operation_unref (o); +} + +static void +req_update_card (GvcMixerControl *control, + int index) +{ + pa_operation *o; + + if (index < 0) { + o = pa_context_get_card_info_list (control->priv->pa_context, + _pa_context_get_card_info_by_index_cb, + control); + } else { + o = pa_context_get_card_info_by_index (control->priv->pa_context, + index, + _pa_context_get_card_info_by_index_cb, + control); + } + + if (o == NULL) { + g_warning ("pa_context_get_card_info_by_index() failed"); + return; + } + pa_operation_unref (o); +} + +static void +req_update_sink_info (GvcMixerControl *control, + int index) +{ + pa_operation *o; + + if (index < 0) { + o = pa_context_get_sink_info_list (control->priv->pa_context, + _pa_context_get_sink_info_cb, + control); + } else { + o = pa_context_get_sink_info_by_index (control->priv->pa_context, + index, + _pa_context_get_sink_info_cb, + control); + } + + if (o == NULL) { + g_warning ("pa_context_get_sink_info_list() failed"); + return; + } + pa_operation_unref (o); +} + +static void +req_update_source_info (GvcMixerControl *control, + int index) +{ + pa_operation *o; + + if (index < 0) { + o = pa_context_get_source_info_list (control->priv->pa_context, + _pa_context_get_source_info_cb, + control); + } else { + o = pa_context_get_source_info_by_index(control->priv->pa_context, + index, + _pa_context_get_source_info_cb, + control); + } + + if (o == NULL) { + g_warning ("pa_context_get_source_info_list() failed"); + return; + } + pa_operation_unref (o); +} + +static void +req_update_sink_input_info (GvcMixerControl *control, + int index) +{ + pa_operation *o; + + if (index < 0) { + o = pa_context_get_sink_input_info_list (control->priv->pa_context, + _pa_context_get_sink_input_info_cb, + control); + } else { + o = pa_context_get_sink_input_info (control->priv->pa_context, + index, + _pa_context_get_sink_input_info_cb, + control); + } + + if (o == NULL) { + g_warning ("pa_context_get_sink_input_info_list() failed"); + return; + } + pa_operation_unref (o); +} + +static void +req_update_source_output_info (GvcMixerControl *control, + int index) +{ + pa_operation *o; + + if (index < 0) { + o = pa_context_get_source_output_info_list (control->priv->pa_context, + _pa_context_get_source_output_info_cb, + control); + } else { + o = pa_context_get_source_output_info (control->priv->pa_context, + index, + _pa_context_get_source_output_info_cb, + control); + } + + if (o == NULL) { + g_warning ("pa_context_get_source_output_info_list() failed"); + return; + } + pa_operation_unref (o); +} + +static void +remove_client (GvcMixerControl *control, + guint index) +{ + g_hash_table_remove (control->priv->clients, + GUINT_TO_POINTER (index)); +} + +static void +remove_card (GvcMixerControl *control, + guint index) +{ + g_hash_table_remove (control->priv->cards, + GUINT_TO_POINTER (index)); + + g_signal_emit (G_OBJECT (control), + signals[CARD_REMOVED], + 0, + index); +} + +static void +remove_sink (GvcMixerControl *control, + guint index) +{ + GvcMixerStream *stream; + +#if 0 + g_debug ("Removing sink: index=%u", index); +#endif + + stream = g_hash_table_lookup (control->priv->sinks, + GUINT_TO_POINTER (index)); + if (stream == NULL) { + return; + } + g_hash_table_remove (control->priv->sinks, + GUINT_TO_POINTER (index)); + + remove_stream (control, stream); +} + +static void +remove_source (GvcMixerControl *control, + guint index) +{ + GvcMixerStream *stream; + +#if 0 + g_debug ("Removing source: index=%u", index); +#endif + + stream = g_hash_table_lookup (control->priv->sources, + GUINT_TO_POINTER (index)); + if (stream == NULL) { + return; + } + g_hash_table_remove (control->priv->sources, + GUINT_TO_POINTER (index)); + + remove_stream (control, stream); +} + +static void +remove_sink_input (GvcMixerControl *control, + guint index) +{ + GvcMixerStream *stream; + +#if 0 + g_debug ("Removing sink input: index=%u", index); +#endif + stream = g_hash_table_lookup (control->priv->sink_inputs, + GUINT_TO_POINTER (index)); + if (stream == NULL) { + return; + } + g_hash_table_remove (control->priv->sink_inputs, + GUINT_TO_POINTER (index)); + + remove_stream (control, stream); +} + +static void +remove_source_output (GvcMixerControl *control, + guint index) +{ + GvcMixerStream *stream; + +#if 0 + g_debug ("Removing source output: index=%u", index); +#endif + + stream = g_hash_table_lookup (control->priv->source_outputs, + GUINT_TO_POINTER (index)); + if (stream == NULL) { + return; + } + g_hash_table_remove (control->priv->source_outputs, + GUINT_TO_POINTER (index)); + + remove_stream (control, stream); +} + +static void +_pa_context_subscribe_cb (pa_context *context, + pa_subscription_event_type_t t, + uint32_t index, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + + switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { + case PA_SUBSCRIPTION_EVENT_SINK: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + remove_sink (control, index); + } else { + req_update_sink_info (control, index); + } + break; + + case PA_SUBSCRIPTION_EVENT_SOURCE: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + remove_source (control, index); + } else { + req_update_source_info (control, index); + } + break; + + case PA_SUBSCRIPTION_EVENT_SINK_INPUT: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + remove_sink_input (control, index); + } else { + req_update_sink_input_info (control, index); + } + break; + + case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + remove_source_output (control, index); + } else { + req_update_source_output_info (control, index); + } + break; + + case PA_SUBSCRIPTION_EVENT_CLIENT: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + remove_client (control, index); + } else { + req_update_client_info (control, index); + } + break; + + case PA_SUBSCRIPTION_EVENT_SERVER: + req_update_server_info (control, index); + break; + + case PA_SUBSCRIPTION_EVENT_CARD: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + remove_card (control, index); + } else { + req_update_card (control, index); + } + break; + } +} + +static void +gvc_mixer_control_ready (GvcMixerControl *control) +{ + pa_operation *o; + + pa_context_set_subscribe_callback (control->priv->pa_context, + _pa_context_subscribe_cb, + control); + o = pa_context_subscribe (control->priv->pa_context, + (pa_subscription_mask_t) + (PA_SUBSCRIPTION_MASK_SINK| + PA_SUBSCRIPTION_MASK_SOURCE| + PA_SUBSCRIPTION_MASK_SINK_INPUT| + PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT| + PA_SUBSCRIPTION_MASK_CLIENT| + PA_SUBSCRIPTION_MASK_SERVER| + PA_SUBSCRIPTION_MASK_CARD), + NULL, + NULL); + + if (o == NULL) { + g_warning ("pa_context_subscribe() failed"); + return; + } + pa_operation_unref (o); + + req_update_server_info (control, -1); + req_update_client_info (control, -1); + req_update_sink_info (control, -1); + req_update_source_info (control, -1); + req_update_sink_input_info (control, -1); + req_update_source_output_info (control, -1); + req_update_card (control, -1); + + control->priv->n_outstanding = 6; + + /* This call is not always supported */ + o = pa_ext_stream_restore_read (control->priv->pa_context, + _pa_ext_stream_restore_read_cb, + control); + if (o != NULL) { + pa_operation_unref (o); + control->priv->n_outstanding++; + + pa_ext_stream_restore_set_subscribe_cb (control->priv->pa_context, + _pa_ext_stream_restore_subscribe_cb, + control); + + o = pa_ext_stream_restore_subscribe (control->priv->pa_context, + 1, + NULL, + NULL); + if (o != NULL) { + pa_operation_unref (o); + } + + } else { + g_debug ("Failed to initialized stream_restore extension: %s", + pa_strerror (pa_context_errno (control->priv->pa_context))); + } +} + +static void +gvc_mixer_new_pa_context (GvcMixerControl *self) +{ + pa_proplist *proplist; + + g_return_if_fail (self); + g_return_if_fail (!self->priv->pa_context); + + proplist = pa_proplist_new (); + pa_proplist_sets (proplist, + PA_PROP_APPLICATION_NAME, + self->priv->name); + pa_proplist_sets (proplist, + PA_PROP_APPLICATION_ID, + "org.gnome.VolumeControl"); + pa_proplist_sets (proplist, + PA_PROP_APPLICATION_ICON_NAME, + "multimedia-volume-control"); + pa_proplist_sets (proplist, + PA_PROP_APPLICATION_VERSION, + PACKAGE_VERSION); + + self->priv->pa_context = pa_context_new_with_proplist (self->priv->pa_api, NULL, proplist); + + pa_proplist_free (proplist); + g_assert (self->priv->pa_context); +} + +static void +remove_all_streams (GvcMixerControl *control, GHashTable *hash_table) +{ + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + remove_stream (control, value); + g_hash_table_iter_remove (&iter); + } +} + +static gboolean +idle_reconnect (gpointer data) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (data); + GHashTableIter iter; + gpointer key, value; + + g_return_val_if_fail (control, FALSE); + + if (control->priv->pa_context) { + pa_context_unref (control->priv->pa_context); + control->priv->pa_context = NULL; + gvc_mixer_new_pa_context (control); + } + + remove_all_streams (control, control->priv->sinks); + remove_all_streams (control, control->priv->sources); + remove_all_streams (control, control->priv->sink_inputs); + remove_all_streams (control, control->priv->source_outputs); + + g_hash_table_iter_init (&iter, control->priv->clients); + while (g_hash_table_iter_next (&iter, &key, &value)) + g_hash_table_iter_remove (&iter); + + gvc_mixer_control_open (control); /* cannot fail */ + + control->priv->reconnect_id = 0; + return FALSE; +} + +static void +_pa_context_state_cb (pa_context *context, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + + switch (pa_context_get_state (context)) { + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + + case PA_CONTEXT_READY: + gvc_mixer_control_ready (control); + break; + + case PA_CONTEXT_FAILED: + g_warning ("Connection failed, reconnecting..."); + if (control->priv->reconnect_id == 0) + control->priv->reconnect_id = g_timeout_add_seconds (RECONNECT_DELAY, idle_reconnect, control); + break; + + case PA_CONTEXT_TERMINATED: + default: + /* FIXME: */ + break; + } +} + +gboolean +gvc_mixer_control_open (GvcMixerControl *control) +{ + int res; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE); + g_return_val_if_fail (control->priv->pa_context != NULL, FALSE); + g_return_val_if_fail (pa_context_get_state (control->priv->pa_context) == PA_CONTEXT_UNCONNECTED, FALSE); + + pa_context_set_state_callback (control->priv->pa_context, + _pa_context_state_cb, + control); + + g_signal_emit (G_OBJECT (control), signals[CONNECTING], 0); + res = pa_context_connect (control->priv->pa_context, NULL, (pa_context_flags_t) PA_CONTEXT_NOFAIL, NULL); + if (res < 0) { + g_warning ("Failed to connect context: %s", + pa_strerror (pa_context_errno (control->priv->pa_context))); + } + + return res; +} + +gboolean +gvc_mixer_control_close (GvcMixerControl *control) +{ + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE); + g_return_val_if_fail (control->priv->pa_context != NULL, FALSE); + + pa_context_disconnect (control->priv->pa_context); + return TRUE; +} + +static void +gvc_mixer_control_dispose (GObject *object) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (object); + + if (control->priv->pa_context != NULL) { + pa_context_unref (control->priv->pa_context); + control->priv->pa_context = NULL; + } + + if (control->priv->default_source_name != NULL) { + g_free (control->priv->default_source_name); + control->priv->default_source_name = NULL; + } + if (control->priv->default_sink_name != NULL) { + g_free (control->priv->default_sink_name); + control->priv->default_sink_name = NULL; + } + + if (control->priv->pa_mainloop != NULL) { + pa_glib_mainloop_free (control->priv->pa_mainloop); + control->priv->pa_mainloop = NULL; + } + + if (control->priv->all_streams != NULL) { + g_hash_table_destroy (control->priv->all_streams); + control->priv->all_streams = NULL; + } + + if (control->priv->sinks != NULL) { + g_hash_table_destroy (control->priv->sinks); + control->priv->sinks = NULL; + } + if (control->priv->sources != NULL) { + g_hash_table_destroy (control->priv->sources); + control->priv->sources = NULL; + } + if (control->priv->sink_inputs != NULL) { + g_hash_table_destroy (control->priv->sink_inputs); + control->priv->sink_inputs = NULL; + } + if (control->priv->source_outputs != NULL) { + g_hash_table_destroy (control->priv->source_outputs); + control->priv->source_outputs = NULL; + } + if (control->priv->clients != NULL) { + g_hash_table_destroy (control->priv->clients); + control->priv->clients = NULL; + } + if (control->priv->cards != NULL) { + g_hash_table_destroy (control->priv->cards); + control->priv->cards = NULL; + } + + G_OBJECT_CLASS (gvc_mixer_control_parent_class)->dispose (object); +} + +static void +gvc_mixer_control_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GvcMixerControl *self = GVC_MIXER_CONTROL (object); + + switch (prop_id) { + case PROP_NAME: + g_free (self->priv->name); + self->priv->name = g_value_dup_string (value); + g_object_notify (G_OBJECT (self), "name"); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gvc_mixer_control_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GvcMixerControl *self = GVC_MIXER_CONTROL (object); + + switch (prop_id) { + case PROP_NAME: + g_value_set_string (value, self->priv->name); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + +static GObject * +gvc_mixer_control_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GvcMixerControl *self; + + object = G_OBJECT_CLASS (gvc_mixer_control_parent_class)->constructor (type, n_construct_properties, construct_params); + + self = GVC_MIXER_CONTROL (object); + + gvc_mixer_new_pa_context (self); + + return object; +} + +static void +gvc_mixer_control_class_init (GvcMixerControlClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructor = gvc_mixer_control_constructor; + object_class->dispose = gvc_mixer_control_dispose; + object_class->finalize = gvc_mixer_control_finalize; + object_class->set_property = gvc_mixer_control_set_property; + object_class->get_property = gvc_mixer_control_get_property; + + g_object_class_install_property (object_class, + PROP_NAME, + g_param_spec_string ("name", + "Name", + "Name to display for this mixer control", + NULL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY)); + + signals [CONNECTING] = + g_signal_new ("connecting", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GvcMixerControlClass, connecting), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals [READY] = + g_signal_new ("ready", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GvcMixerControlClass, ready), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals [STREAM_ADDED] = + g_signal_new ("stream-added", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GvcMixerControlClass, stream_added), + NULL, NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, 1, G_TYPE_UINT); + signals [STREAM_REMOVED] = + g_signal_new ("stream-removed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GvcMixerControlClass, stream_removed), + NULL, NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, 1, G_TYPE_UINT); + signals [CARD_ADDED] = + g_signal_new ("card-added", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GvcMixerControlClass, card_added), + NULL, NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, 1, G_TYPE_UINT); + signals [CARD_REMOVED] = + g_signal_new ("card-removed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GvcMixerControlClass, card_removed), + NULL, NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, 1, G_TYPE_UINT); + signals [DEFAULT_SINK_CHANGED] = + g_signal_new ("default-sink-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GvcMixerControlClass, default_sink_changed), + NULL, NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, 1, G_TYPE_UINT); + signals [DEFAULT_SOURCE_CHANGED] = + g_signal_new ("default-source-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GvcMixerControlClass, default_source_changed), + NULL, NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, 1, G_TYPE_UINT); + + g_type_class_add_private (klass, sizeof (GvcMixerControlPrivate)); +} + +static void +gvc_mixer_control_init (GvcMixerControl *control) +{ + control->priv = GVC_MIXER_CONTROL_GET_PRIVATE (control); + + control->priv->pa_mainloop = pa_glib_mainloop_new (g_main_context_default ()); + g_assert (control->priv->pa_mainloop); + + control->priv->pa_api = pa_glib_mainloop_get_api (control->priv->pa_mainloop); + g_assert (control->priv->pa_api); + + control->priv->all_streams = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref); + control->priv->sinks = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref); + control->priv->sources = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref); + control->priv->sink_inputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref); + control->priv->source_outputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref); + control->priv->cards = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref); + + control->priv->clients = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_free); +} + +static void +gvc_mixer_control_finalize (GObject *object) +{ + GvcMixerControl *mixer_control; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_CONTROL (object)); + + mixer_control = GVC_MIXER_CONTROL (object); + g_free (mixer_control->priv->name); + mixer_control->priv->name = NULL; + + g_return_if_fail (mixer_control->priv != NULL); + G_OBJECT_CLASS (gvc_mixer_control_parent_class)->finalize (object); +} + +GvcMixerControl * +gvc_mixer_control_new (const char *name) +{ + GObject *control; + control = g_object_new (GVC_TYPE_MIXER_CONTROL, + "name", name, + NULL); + return GVC_MIXER_CONTROL (control); +} diff --git a/src/gvc/gvc-mixer-control.h b/src/gvc/gvc-mixer-control.h new file mode 100644 index 000000000..d32b20493 --- /dev/null +++ b/src/gvc/gvc-mixer-control.h @@ -0,0 +1,96 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __GVC_MIXER_CONTROL_H +#define __GVC_MIXER_CONTROL_H + +#include +#include "gvc-mixer-stream.h" +#include "gvc-mixer-card.h" + +G_BEGIN_DECLS + +#define GVC_TYPE_MIXER_CONTROL (gvc_mixer_control_get_type ()) +#define GVC_MIXER_CONTROL(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_CONTROL, GvcMixerControl)) +#define GVC_MIXER_CONTROL_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_CONTROL, GvcMixerControlClass)) +#define GVC_IS_MIXER_CONTROL(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_CONTROL)) +#define GVC_IS_MIXER_CONTROL_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_CONTROL)) +#define GVC_MIXER_CONTROL_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_CONTROL, GvcMixerControlClass)) + +typedef struct GvcMixerControlPrivate GvcMixerControlPrivate; + +typedef struct +{ + GObject parent; + GvcMixerControlPrivate *priv; +} GvcMixerControl; + +typedef struct +{ + GObjectClass parent_class; + + void (*connecting) (GvcMixerControl *control); + void (*ready) (GvcMixerControl *control); + void (*stream_added) (GvcMixerControl *control, + guint id); + void (*stream_removed) (GvcMixerControl *control, + guint id); + void (*card_added) (GvcMixerControl *control, + guint id); + void (*card_removed) (GvcMixerControl *control, + guint id); + void (*default_sink_changed) (GvcMixerControl *control, + guint id); + void (*default_source_changed) (GvcMixerControl *control, + guint id); +} GvcMixerControlClass; + +GType gvc_mixer_control_get_type (void); + +GvcMixerControl * gvc_mixer_control_new (const char *name); + +gboolean gvc_mixer_control_open (GvcMixerControl *control); +gboolean gvc_mixer_control_close (GvcMixerControl *control); +gboolean gvc_mixer_control_is_ready (GvcMixerControl *control); + +GSList * gvc_mixer_control_get_cards (GvcMixerControl *control); +GSList * gvc_mixer_control_get_streams (GvcMixerControl *control); +GSList * gvc_mixer_control_get_sinks (GvcMixerControl *control); +GSList * gvc_mixer_control_get_sources (GvcMixerControl *control); +GSList * gvc_mixer_control_get_sink_inputs (GvcMixerControl *control); +GSList * gvc_mixer_control_get_source_outputs (GvcMixerControl *control); + +GvcMixerStream * gvc_mixer_control_lookup_stream_id (GvcMixerControl *control, + guint id); +GvcMixerCard * gvc_mixer_control_lookup_card_id (GvcMixerControl *control, + guint id); + +GvcMixerStream * gvc_mixer_control_get_default_sink (GvcMixerControl *control); +GvcMixerStream * gvc_mixer_control_get_default_source (GvcMixerControl *control); +GvcMixerStream * gvc_mixer_control_get_event_sink_input (GvcMixerControl *control); + +gboolean gvc_mixer_control_set_default_sink (GvcMixerControl *control, + GvcMixerStream *stream); +gboolean gvc_mixer_control_set_default_source (GvcMixerControl *control, + GvcMixerStream *stream); + +G_END_DECLS + +#endif /* __GVC_MIXER_CONTROL_H */ diff --git a/src/gvc/gvc-mixer-event-role.c b/src/gvc/gvc-mixer-event-role.c new file mode 100644 index 000000000..7eb3d00d7 --- /dev/null +++ b/src/gvc/gvc-mixer-event-role.c @@ -0,0 +1,250 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 William Jon McCann + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include "config.h" + +#include +#include +#include + +#include +#include + +#include +#include + +#include "gvc-mixer-event-role.h" +#include "gvc-mixer-stream-private.h" +#include "gvc-channel-map-private.h" + +#define GVC_MIXER_EVENT_ROLE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_EVENT_ROLE, GvcMixerEventRolePrivate)) + +struct GvcMixerEventRolePrivate +{ + char *device; +}; + +enum +{ + PROP_0, + PROP_DEVICE +}; + +static void gvc_mixer_event_role_class_init (GvcMixerEventRoleClass *klass); +static void gvc_mixer_event_role_init (GvcMixerEventRole *mixer_event_role); +static void gvc_mixer_event_role_finalize (GObject *object); + +G_DEFINE_TYPE (GvcMixerEventRole, gvc_mixer_event_role, GVC_TYPE_MIXER_STREAM) + +static gboolean +update_settings (GvcMixerEventRole *role, + gboolean is_muted, + gpointer *op) +{ + pa_operation *o; + guint index; + const GvcChannelMap *map; + pa_context *context; + pa_ext_stream_restore_info info; + + index = gvc_mixer_stream_get_index (GVC_MIXER_STREAM (role)); + + map = gvc_mixer_stream_get_channel_map (GVC_MIXER_STREAM(role)); + + info.volume = *gvc_channel_map_get_cvolume(map); + info.name = "sink-input-by-media-role:event"; + info.channel_map = *gvc_channel_map_get_pa_channel_map(map); + info.device = role->priv->device; + info.mute = is_muted; + + context = gvc_mixer_stream_get_pa_context (GVC_MIXER_STREAM (role)); + + o = pa_ext_stream_restore_write (context, + PA_UPDATE_REPLACE, + &info, + 1, + TRUE, + NULL, + NULL); + + if (o == NULL) { + g_warning ("pa_ext_stream_restore_write() failed"); + return FALSE; + } + + if (op != NULL) + *op = o; + + return TRUE; +} + +static gboolean +gvc_mixer_event_role_push_volume (GvcMixerStream *stream, gpointer *op) +{ + return update_settings (GVC_MIXER_EVENT_ROLE (stream), + gvc_mixer_stream_get_is_muted (stream), op); +} + +static gboolean +gvc_mixer_event_role_change_is_muted (GvcMixerStream *stream, + gboolean is_muted) +{ + return update_settings (GVC_MIXER_EVENT_ROLE (stream), + is_muted, NULL); +} + +static gboolean +gvc_mixer_event_role_set_device (GvcMixerEventRole *role, + const char *device) +{ + g_return_val_if_fail (GVC_IS_MIXER_EVENT_ROLE (role), FALSE); + + g_free (role->priv->device); + role->priv->device = g_strdup (device); + g_object_notify (G_OBJECT (role), "device"); + + return TRUE; +} + +static void +gvc_mixer_event_role_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GvcMixerEventRole *self = GVC_MIXER_EVENT_ROLE (object); + + switch (prop_id) { + case PROP_DEVICE: + gvc_mixer_event_role_set_device (self, g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gvc_mixer_event_role_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GvcMixerEventRole *self = GVC_MIXER_EVENT_ROLE (object); + + switch (prop_id) { + case PROP_DEVICE: + g_value_set_string (value, self->priv->device); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GObject * +gvc_mixer_event_role_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GvcMixerEventRole *self; + + object = G_OBJECT_CLASS (gvc_mixer_event_role_parent_class)->constructor (type, n_construct_properties, construct_params); + + self = GVC_MIXER_EVENT_ROLE (object); + + return object; +} + +static void +gvc_mixer_event_role_class_init (GvcMixerEventRoleClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GvcMixerStreamClass *stream_class = GVC_MIXER_STREAM_CLASS (klass); + + object_class->constructor = gvc_mixer_event_role_constructor; + object_class->finalize = gvc_mixer_event_role_finalize; + object_class->set_property = gvc_mixer_event_role_set_property; + object_class->get_property = gvc_mixer_event_role_get_property; + + stream_class->push_volume = gvc_mixer_event_role_push_volume; + stream_class->change_is_muted = gvc_mixer_event_role_change_is_muted; + + g_object_class_install_property (object_class, + PROP_DEVICE, + g_param_spec_string ("device", + "Device", + "Device", + NULL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + + g_type_class_add_private (klass, sizeof (GvcMixerEventRolePrivate)); +} + +static void +gvc_mixer_event_role_init (GvcMixerEventRole *event_role) +{ + event_role->priv = GVC_MIXER_EVENT_ROLE_GET_PRIVATE (event_role); + +} + +static void +gvc_mixer_event_role_finalize (GObject *object) +{ + GvcMixerEventRole *mixer_event_role; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_EVENT_ROLE (object)); + + mixer_event_role = GVC_MIXER_EVENT_ROLE (object); + + g_return_if_fail (mixer_event_role->priv != NULL); + + g_free (mixer_event_role->priv->device); + + G_OBJECT_CLASS (gvc_mixer_event_role_parent_class)->finalize (object); +} + +/** + * gvc_mixer_event_role_new: (skip) + * + * @context: + * @index: + * @channel_map: + * + * Returns: + */ +GvcMixerStream * +gvc_mixer_event_role_new (pa_context *context, + const char *device, + GvcChannelMap *channel_map) +{ + GObject *object; + + object = g_object_new (GVC_TYPE_MIXER_EVENT_ROLE, + "pa-context", context, + "index", 0, + "device", device, + "channel-map", channel_map, + NULL); + + return GVC_MIXER_STREAM (object); +} diff --git a/src/gvc/gvc-mixer-event-role.h b/src/gvc/gvc-mixer-event-role.h new file mode 100644 index 000000000..ab4c509e5 --- /dev/null +++ b/src/gvc/gvc-mixer-event-role.h @@ -0,0 +1,57 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __GVC_MIXER_EVENT_ROLE_H +#define __GVC_MIXER_EVENT_ROLE_H + +#include +#include "gvc-mixer-stream.h" + +G_BEGIN_DECLS + +#define GVC_TYPE_MIXER_EVENT_ROLE (gvc_mixer_event_role_get_type ()) +#define GVC_MIXER_EVENT_ROLE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_EVENT_ROLE, GvcMixerEventRole)) +#define GVC_MIXER_EVENT_ROLE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_EVENT_ROLE, GvcMixerEventRoleClass)) +#define GVC_IS_MIXER_EVENT_ROLE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_EVENT_ROLE)) +#define GVC_IS_MIXER_EVENT_ROLE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_EVENT_ROLE)) +#define GVC_MIXER_EVENT_ROLE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_EVENT_ROLE, GvcMixerEventRoleClass)) + +typedef struct GvcMixerEventRolePrivate GvcMixerEventRolePrivate; + +typedef struct +{ + GvcMixerStream parent; + GvcMixerEventRolePrivate *priv; +} GvcMixerEventRole; + +typedef struct +{ + GvcMixerStreamClass parent_class; +} GvcMixerEventRoleClass; + +GType gvc_mixer_event_role_get_type (void); + +GvcMixerStream * gvc_mixer_event_role_new (pa_context *context, + const char *device, + GvcChannelMap *channel_map); + +G_END_DECLS + +#endif /* __GVC_MIXER_EVENT_ROLE_H */ diff --git a/src/gvc/gvc-mixer-sink-input.c b/src/gvc/gvc-mixer-sink-input.c new file mode 100644 index 000000000..9429eca0a --- /dev/null +++ b/src/gvc/gvc-mixer-sink-input.c @@ -0,0 +1,199 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 William Jon McCann + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include "config.h" + +#include +#include +#include + +#include +#include + +#include + +#include "gvc-mixer-sink-input.h" +#include "gvc-mixer-stream-private.h" +#include "gvc-channel-map-private.h" + +#define GVC_MIXER_SINK_INPUT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_SINK_INPUT, GvcMixerSinkInputPrivate)) + +struct GvcMixerSinkInputPrivate +{ + gpointer dummy; +}; + +static void gvc_mixer_sink_input_class_init (GvcMixerSinkInputClass *klass); +static void gvc_mixer_sink_input_init (GvcMixerSinkInput *mixer_sink_input); +static void gvc_mixer_sink_input_finalize (GObject *object); +static void gvc_mixer_sink_input_dispose (GObject *object); + +G_DEFINE_TYPE (GvcMixerSinkInput, gvc_mixer_sink_input, GVC_TYPE_MIXER_STREAM) + +static gboolean +gvc_mixer_sink_input_push_volume (GvcMixerStream *stream, gpointer *op) +{ + pa_operation *o; + guint index; + const GvcChannelMap *map; + pa_context *context; + const pa_cvolume *cv; + guint num_channels; + + index = gvc_mixer_stream_get_index (stream); + + map = gvc_mixer_stream_get_channel_map (stream); + num_channels = gvc_channel_map_get_num_channels (map); + + cv = gvc_channel_map_get_cvolume(map); + + context = gvc_mixer_stream_get_pa_context (stream); + + o = pa_context_set_sink_input_volume (context, + index, + cv, + NULL, + NULL); + + if (o == NULL) { + g_warning ("pa_context_set_sink_input_volume() failed"); + return FALSE; + } + + *op = o; + + return TRUE; +} + +static gboolean +gvc_mixer_sink_input_change_is_muted (GvcMixerStream *stream, + gboolean is_muted) +{ + pa_operation *o; + guint index; + pa_context *context; + + index = gvc_mixer_stream_get_index (stream); + context = gvc_mixer_stream_get_pa_context (stream); + + o = pa_context_set_sink_input_mute (context, + index, + is_muted, + NULL, + NULL); + + if (o == NULL) { + g_warning ("pa_context_set_sink_input_mute_by_index() failed"); + return FALSE; + } + + pa_operation_unref(o); + + return TRUE; +} + +static GObject * +gvc_mixer_sink_input_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GvcMixerSinkInput *self; + + object = G_OBJECT_CLASS (gvc_mixer_sink_input_parent_class)->constructor (type, n_construct_properties, construct_params); + + self = GVC_MIXER_SINK_INPUT (object); + + return object; +} + +static void +gvc_mixer_sink_input_class_init (GvcMixerSinkInputClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GvcMixerStreamClass *stream_class = GVC_MIXER_STREAM_CLASS (klass); + + object_class->constructor = gvc_mixer_sink_input_constructor; + object_class->dispose = gvc_mixer_sink_input_dispose; + object_class->finalize = gvc_mixer_sink_input_finalize; + + stream_class->push_volume = gvc_mixer_sink_input_push_volume; + stream_class->change_is_muted = gvc_mixer_sink_input_change_is_muted; + + g_type_class_add_private (klass, sizeof (GvcMixerSinkInputPrivate)); +} + +static void +gvc_mixer_sink_input_init (GvcMixerSinkInput *sink_input) +{ + sink_input->priv = GVC_MIXER_SINK_INPUT_GET_PRIVATE (sink_input); +} + +static void +gvc_mixer_sink_input_dispose (GObject *object) +{ + GvcMixerSinkInput *mixer_sink_input; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_SINK_INPUT (object)); + + mixer_sink_input = GVC_MIXER_SINK_INPUT (object); + + G_OBJECT_CLASS (gvc_mixer_sink_input_parent_class)->dispose (object); +} + +static void +gvc_mixer_sink_input_finalize (GObject *object) +{ + GvcMixerSinkInput *mixer_sink_input; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_SINK_INPUT (object)); + + mixer_sink_input = GVC_MIXER_SINK_INPUT (object); + + g_return_if_fail (mixer_sink_input->priv != NULL); + G_OBJECT_CLASS (gvc_mixer_sink_input_parent_class)->finalize (object); +} + +/** + * gvc_mixer_sink_input_new: (skip) + * + * @context: + * @index: + * @channel_map: + * + * Returns: + */ +GvcMixerStream * +gvc_mixer_sink_input_new (pa_context *context, + guint index, + GvcChannelMap *channel_map) +{ + GObject *object; + + object = g_object_new (GVC_TYPE_MIXER_SINK_INPUT, + "pa-context", context, + "index", index, + "channel-map", channel_map, + NULL); + + return GVC_MIXER_STREAM (object); +} diff --git a/src/gvc/gvc-mixer-sink-input.h b/src/gvc/gvc-mixer-sink-input.h new file mode 100644 index 000000000..8a4b71454 --- /dev/null +++ b/src/gvc/gvc-mixer-sink-input.h @@ -0,0 +1,57 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __GVC_MIXER_SINK_INPUT_H +#define __GVC_MIXER_SINK_INPUT_H + +#include +#include "gvc-mixer-stream.h" + +G_BEGIN_DECLS + +#define GVC_TYPE_MIXER_SINK_INPUT (gvc_mixer_sink_input_get_type ()) +#define GVC_MIXER_SINK_INPUT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_SINK_INPUT, GvcMixerSinkInput)) +#define GVC_MIXER_SINK_INPUT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_SINK_INPUT, GvcMixerSinkInputClass)) +#define GVC_IS_MIXER_SINK_INPUT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_SINK_INPUT)) +#define GVC_IS_MIXER_SINK_INPUT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_SINK_INPUT)) +#define GVC_MIXER_SINK_INPUT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_SINK_INPUT, GvcMixerSinkInputClass)) + +typedef struct GvcMixerSinkInputPrivate GvcMixerSinkInputPrivate; + +typedef struct +{ + GvcMixerStream parent; + GvcMixerSinkInputPrivate *priv; +} GvcMixerSinkInput; + +typedef struct +{ + GvcMixerStreamClass parent_class; +} GvcMixerSinkInputClass; + +GType gvc_mixer_sink_input_get_type (void); + +GvcMixerStream * gvc_mixer_sink_input_new (pa_context *context, + guint index, + GvcChannelMap *map); + +G_END_DECLS + +#endif /* __GVC_MIXER_SINK_INPUT_H */ diff --git a/src/gvc/gvc-mixer-sink.c b/src/gvc/gvc-mixer-sink.c new file mode 100644 index 000000000..30fceac4d --- /dev/null +++ b/src/gvc/gvc-mixer-sink.c @@ -0,0 +1,231 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 William Jon McCann + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include "config.h" + +#include +#include +#include + +#include +#include + +#include + +#include "gvc-mixer-sink.h" +#include "gvc-mixer-stream-private.h" +#include "gvc-channel-map-private.h" + +#define GVC_MIXER_SINK_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_SINK, GvcMixerSinkPrivate)) + +struct GvcMixerSinkPrivate +{ + gpointer dummy; +}; + +static void gvc_mixer_sink_class_init (GvcMixerSinkClass *klass); +static void gvc_mixer_sink_init (GvcMixerSink *mixer_sink); +static void gvc_mixer_sink_finalize (GObject *object); +static void gvc_mixer_sink_dispose (GObject *object); + +G_DEFINE_TYPE (GvcMixerSink, gvc_mixer_sink, GVC_TYPE_MIXER_STREAM) + +static gboolean +gvc_mixer_sink_push_volume (GvcMixerStream *stream, gpointer *op) +{ + pa_operation *o; + guint index; + const GvcChannelMap *map; + pa_context *context; + const pa_cvolume *cv; + + index = gvc_mixer_stream_get_index (stream); + + map = gvc_mixer_stream_get_channel_map (stream); + + /* set the volume */ + cv = gvc_channel_map_get_cvolume(map); + + context = gvc_mixer_stream_get_pa_context (stream); + + o = pa_context_set_sink_volume_by_index (context, + index, + cv, + NULL, + NULL); + + if (o == NULL) { + g_warning ("pa_context_set_sink_volume_by_index() failed: %s", pa_strerror(pa_context_errno(context))); + return FALSE; + } + + *op = o; + + return TRUE; +} + +static gboolean +gvc_mixer_sink_change_is_muted (GvcMixerStream *stream, + gboolean is_muted) +{ + pa_operation *o; + guint index; + pa_context *context; + + index = gvc_mixer_stream_get_index (stream); + context = gvc_mixer_stream_get_pa_context (stream); + + o = pa_context_set_sink_mute_by_index (context, + index, + is_muted, + NULL, + NULL); + + if (o == NULL) { + g_warning ("pa_context_set_sink_mute_by_index() failed: %s", pa_strerror(pa_context_errno(context))); + return FALSE; + } + + pa_operation_unref(o); + + return TRUE; +} + +static gboolean +gvc_mixer_sink_change_port (GvcMixerStream *stream, + const char *port) +{ +#if PA_MICRO > 15 + pa_operation *o; + guint index; + pa_context *context; + + index = gvc_mixer_stream_get_index (stream); + context = gvc_mixer_stream_get_pa_context (stream); + + o = pa_context_set_sink_port_by_index (context, + index, + port, + NULL, + NULL); + + if (o == NULL) { + g_warning ("pa_context_set_sink_port_by_index() failed: %s", pa_strerror(pa_context_errno(context))); + return FALSE; + } + + pa_operation_unref(o); + + return TRUE; +#else + return FALSE; +#endif /* PA_MICRO > 15 */ +} + +static GObject * +gvc_mixer_sink_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GvcMixerSink *self; + + object = G_OBJECT_CLASS (gvc_mixer_sink_parent_class)->constructor (type, n_construct_properties, construct_params); + + self = GVC_MIXER_SINK (object); + + return object; +} + +static void +gvc_mixer_sink_class_init (GvcMixerSinkClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GvcMixerStreamClass *stream_class = GVC_MIXER_STREAM_CLASS (klass); + + object_class->constructor = gvc_mixer_sink_constructor; + object_class->dispose = gvc_mixer_sink_dispose; + object_class->finalize = gvc_mixer_sink_finalize; + + stream_class->push_volume = gvc_mixer_sink_push_volume; + stream_class->change_port = gvc_mixer_sink_change_port; + stream_class->change_is_muted = gvc_mixer_sink_change_is_muted; + + g_type_class_add_private (klass, sizeof (GvcMixerSinkPrivate)); +} + +static void +gvc_mixer_sink_init (GvcMixerSink *sink) +{ + sink->priv = GVC_MIXER_SINK_GET_PRIVATE (sink); +} + +static void +gvc_mixer_sink_dispose (GObject *object) +{ + GvcMixerSink *mixer_sink; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_SINK (object)); + + mixer_sink = GVC_MIXER_SINK (object); + + G_OBJECT_CLASS (gvc_mixer_sink_parent_class)->dispose (object); +} + +static void +gvc_mixer_sink_finalize (GObject *object) +{ + GvcMixerSink *mixer_sink; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_SINK (object)); + + mixer_sink = GVC_MIXER_SINK (object); + + g_return_if_fail (mixer_sink->priv != NULL); + G_OBJECT_CLASS (gvc_mixer_sink_parent_class)->finalize (object); +} + +/** + * gvc_mixer_sink_new: (skip) + * + * @context: + * @index: + * @channel_map: + * + * Returns: + */ +GvcMixerStream * +gvc_mixer_sink_new (pa_context *context, + guint index, + GvcChannelMap *channel_map) + +{ + GObject *object; + + object = g_object_new (GVC_TYPE_MIXER_SINK, + "pa-context", context, + "index", index, + "channel-map", channel_map, + NULL); + + return GVC_MIXER_STREAM (object); +} diff --git a/src/gvc/gvc-mixer-sink.h b/src/gvc/gvc-mixer-sink.h new file mode 100644 index 000000000..2a4a4badb --- /dev/null +++ b/src/gvc/gvc-mixer-sink.h @@ -0,0 +1,57 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __GVC_MIXER_SINK_H +#define __GVC_MIXER_SINK_H + +#include +#include "gvc-mixer-stream.h" + +G_BEGIN_DECLS + +#define GVC_TYPE_MIXER_SINK (gvc_mixer_sink_get_type ()) +#define GVC_MIXER_SINK(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_SINK, GvcMixerSink)) +#define GVC_MIXER_SINK_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_SINK, GvcMixerSinkClass)) +#define GVC_IS_MIXER_SINK(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_SINK)) +#define GVC_IS_MIXER_SINK_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_SINK)) +#define GVC_MIXER_SINK_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_SINK, GvcMixerSinkClass)) + +typedef struct GvcMixerSinkPrivate GvcMixerSinkPrivate; + +typedef struct +{ + GvcMixerStream parent; + GvcMixerSinkPrivate *priv; +} GvcMixerSink; + +typedef struct +{ + GvcMixerStreamClass parent_class; +} GvcMixerSinkClass; + +GType gvc_mixer_sink_get_type (void); + +GvcMixerStream * gvc_mixer_sink_new (pa_context *context, + guint index, + GvcChannelMap *map); + +G_END_DECLS + +#endif /* __GVC_MIXER_SINK_H */ diff --git a/src/gvc/gvc-mixer-source-output.c b/src/gvc/gvc-mixer-source-output.c new file mode 100644 index 000000000..636fc2ea7 --- /dev/null +++ b/src/gvc/gvc-mixer-source-output.c @@ -0,0 +1,137 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 William Jon McCann + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include "config.h" + +#include +#include +#include + +#include +#include + +#include + +#include "gvc-mixer-source-output.h" + +#define GVC_MIXER_SOURCE_OUTPUT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_SOURCE_OUTPUT, GvcMixerSourceOutputPrivate)) + +struct GvcMixerSourceOutputPrivate +{ + gpointer dummy; +}; + +static void gvc_mixer_source_output_class_init (GvcMixerSourceOutputClass *klass); +static void gvc_mixer_source_output_init (GvcMixerSourceOutput *mixer_source_output); +static void gvc_mixer_source_output_finalize (GObject *object); + +G_DEFINE_TYPE (GvcMixerSourceOutput, gvc_mixer_source_output, GVC_TYPE_MIXER_STREAM) + +static gboolean +gvc_mixer_source_output_push_volume (GvcMixerStream *stream, gpointer *op) +{ + /* FIXME: */ + *op = NULL; + return TRUE; +} + +static gboolean +gvc_mixer_source_output_change_is_muted (GvcMixerStream *stream, + gboolean is_muted) +{ + /* FIXME: */ + return TRUE; +} + +static GObject * +gvc_mixer_source_output_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GvcMixerSourceOutput *self; + + object = G_OBJECT_CLASS (gvc_mixer_source_output_parent_class)->constructor (type, n_construct_properties, construct_params); + + self = GVC_MIXER_SOURCE_OUTPUT (object); + + return object; +} + +static void +gvc_mixer_source_output_class_init (GvcMixerSourceOutputClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GvcMixerStreamClass *stream_class = GVC_MIXER_STREAM_CLASS (klass); + + object_class->constructor = gvc_mixer_source_output_constructor; + object_class->finalize = gvc_mixer_source_output_finalize; + + stream_class->push_volume = gvc_mixer_source_output_push_volume; + stream_class->change_is_muted = gvc_mixer_source_output_change_is_muted; + + g_type_class_add_private (klass, sizeof (GvcMixerSourceOutputPrivate)); +} + +static void +gvc_mixer_source_output_init (GvcMixerSourceOutput *source_output) +{ + source_output->priv = GVC_MIXER_SOURCE_OUTPUT_GET_PRIVATE (source_output); + +} + +static void +gvc_mixer_source_output_finalize (GObject *object) +{ + GvcMixerSourceOutput *mixer_source_output; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_SOURCE_OUTPUT (object)); + + mixer_source_output = GVC_MIXER_SOURCE_OUTPUT (object); + + g_return_if_fail (mixer_source_output->priv != NULL); + G_OBJECT_CLASS (gvc_mixer_source_output_parent_class)->finalize (object); +} + +/** + * gvc_mixer_source_output_new: (skip) + * + * @context: + * @index: + * @channel_map: + * + * Returns: + */ +GvcMixerStream * +gvc_mixer_source_output_new (pa_context *context, + guint index, + GvcChannelMap *channel_map) +{ + GObject *object; + + object = g_object_new (GVC_TYPE_MIXER_SOURCE_OUTPUT, + "pa-context", context, + "index", index, + "channel-map", channel_map, + NULL); + + return GVC_MIXER_STREAM (object); +} diff --git a/src/gvc/gvc-mixer-source-output.h b/src/gvc/gvc-mixer-source-output.h new file mode 100644 index 000000000..2283e3b67 --- /dev/null +++ b/src/gvc/gvc-mixer-source-output.h @@ -0,0 +1,57 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __GVC_MIXER_SOURCE_OUTPUT_H +#define __GVC_MIXER_SOURCE_OUTPUT_H + +#include +#include "gvc-mixer-stream.h" + +G_BEGIN_DECLS + +#define GVC_TYPE_MIXER_SOURCE_OUTPUT (gvc_mixer_source_output_get_type ()) +#define GVC_MIXER_SOURCE_OUTPUT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_SOURCE_OUTPUT, GvcMixerSourceOutput)) +#define GVC_MIXER_SOURCE_OUTPUT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_SOURCE_OUTPUT, GvcMixerSourceOutputClass)) +#define GVC_IS_MIXER_SOURCE_OUTPUT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_SOURCE_OUTPUT)) +#define GVC_IS_MIXER_SOURCE_OUTPUT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_SOURCE_OUTPUT)) +#define GVC_MIXER_SOURCE_OUTPUT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_SOURCE_OUTPUT, GvcMixerSourceOutputClass)) + +typedef struct GvcMixerSourceOutputPrivate GvcMixerSourceOutputPrivate; + +typedef struct +{ + GvcMixerStream parent; + GvcMixerSourceOutputPrivate *priv; +} GvcMixerSourceOutput; + +typedef struct +{ + GvcMixerStreamClass parent_class; +} GvcMixerSourceOutputClass; + +GType gvc_mixer_source_output_get_type (void); + +GvcMixerStream * gvc_mixer_source_output_new (pa_context *context, + guint index, + GvcChannelMap *map); + +G_END_DECLS + +#endif /* __GVC_MIXER_SOURCE_OUTPUT_H */ diff --git a/src/gvc/gvc-mixer-source.c b/src/gvc/gvc-mixer-source.c new file mode 100644 index 000000000..46d640380 --- /dev/null +++ b/src/gvc/gvc-mixer-source.c @@ -0,0 +1,231 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 William Jon McCann + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include "config.h" + +#include +#include +#include + +#include +#include + +#include + +#include "gvc-mixer-source.h" +#include "gvc-mixer-stream-private.h" +#include "gvc-channel-map-private.h" + +#define GVC_MIXER_SOURCE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_SOURCE, GvcMixerSourcePrivate)) + +struct GvcMixerSourcePrivate +{ + gpointer dummy; +}; + +static void gvc_mixer_source_class_init (GvcMixerSourceClass *klass); +static void gvc_mixer_source_init (GvcMixerSource *mixer_source); +static void gvc_mixer_source_finalize (GObject *object); +static void gvc_mixer_source_dispose (GObject *object); + +G_DEFINE_TYPE (GvcMixerSource, gvc_mixer_source, GVC_TYPE_MIXER_STREAM) + +static gboolean +gvc_mixer_source_push_volume (GvcMixerStream *stream, gpointer *op) +{ + pa_operation *o; + guint index; + const GvcChannelMap *map; + pa_context *context; + const pa_cvolume *cv; + + index = gvc_mixer_stream_get_index (stream); + + map = gvc_mixer_stream_get_channel_map (stream); + + /* set the volume */ + cv = gvc_channel_map_get_cvolume (map); + + context = gvc_mixer_stream_get_pa_context (stream); + + o = pa_context_set_source_volume_by_index (context, + index, + cv, + NULL, + NULL); + + if (o == NULL) { + g_warning ("pa_context_set_source_volume_by_index() failed: %s", pa_strerror(pa_context_errno(context))); + return FALSE; + } + + *op = o; + + return TRUE; +} + +static gboolean +gvc_mixer_source_change_is_muted (GvcMixerStream *stream, + gboolean is_muted) +{ + pa_operation *o; + guint index; + pa_context *context; + + index = gvc_mixer_stream_get_index (stream); + context = gvc_mixer_stream_get_pa_context (stream); + + o = pa_context_set_source_mute_by_index (context, + index, + is_muted, + NULL, + NULL); + + if (o == NULL) { + g_warning ("pa_context_set_source_mute_by_index() failed: %s", pa_strerror(pa_context_errno(context))); + return FALSE; + } + + pa_operation_unref(o); + + return TRUE; +} + +static gboolean +gvc_mixer_source_change_port (GvcMixerStream *stream, + const char *port) +{ +#if PA_MICRO > 15 + pa_operation *o; + guint index; + pa_context *context; + + index = gvc_mixer_stream_get_index (stream); + context = gvc_mixer_stream_get_pa_context (stream); + + o = pa_context_set_source_port_by_index (context, + index, + port, + NULL, + NULL); + + if (o == NULL) { + g_warning ("pa_context_set_source_port_by_index() failed: %s", pa_strerror(pa_context_errno(context))); + return FALSE; + } + + pa_operation_unref(o); + + return TRUE; +#else + return FALSE; +#endif /* PA_MICRO > 15 */ +} + +static GObject * +gvc_mixer_source_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GvcMixerSource *self; + + object = G_OBJECT_CLASS (gvc_mixer_source_parent_class)->constructor (type, n_construct_properties, construct_params); + + self = GVC_MIXER_SOURCE (object); + + return object; +} + +static void +gvc_mixer_source_class_init (GvcMixerSourceClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GvcMixerStreamClass *stream_class = GVC_MIXER_STREAM_CLASS (klass); + + object_class->constructor = gvc_mixer_source_constructor; + object_class->dispose = gvc_mixer_source_dispose; + object_class->finalize = gvc_mixer_source_finalize; + + stream_class->push_volume = gvc_mixer_source_push_volume; + stream_class->change_is_muted = gvc_mixer_source_change_is_muted; + stream_class->change_port = gvc_mixer_source_change_port; + + g_type_class_add_private (klass, sizeof (GvcMixerSourcePrivate)); +} + +static void +gvc_mixer_source_init (GvcMixerSource *source) +{ + source->priv = GVC_MIXER_SOURCE_GET_PRIVATE (source); +} + +static void +gvc_mixer_source_dispose (GObject *object) +{ + GvcMixerSource *mixer_source; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_SOURCE (object)); + + mixer_source = GVC_MIXER_SOURCE (object); + + G_OBJECT_CLASS (gvc_mixer_source_parent_class)->dispose (object); +} + +static void +gvc_mixer_source_finalize (GObject *object) +{ + GvcMixerSource *mixer_source; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_SOURCE (object)); + + mixer_source = GVC_MIXER_SOURCE (object); + + g_return_if_fail (mixer_source->priv != NULL); + G_OBJECT_CLASS (gvc_mixer_source_parent_class)->finalize (object); +} + +/** + * gvc_mixer_source_new: (skip) + * + * @context: + * @index: + * @channel_map: + * + * Returns: + */ +GvcMixerStream * +gvc_mixer_source_new (pa_context *context, + guint index, + GvcChannelMap *channel_map) + +{ + GObject *object; + + object = g_object_new (GVC_TYPE_MIXER_SOURCE, + "pa-context", context, + "index", index, + "channel-map", channel_map, + NULL); + + return GVC_MIXER_STREAM (object); +} diff --git a/src/gvc/gvc-mixer-source.h b/src/gvc/gvc-mixer-source.h new file mode 100644 index 000000000..503f1b54d --- /dev/null +++ b/src/gvc/gvc-mixer-source.h @@ -0,0 +1,57 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __GVC_MIXER_SOURCE_H +#define __GVC_MIXER_SOURCE_H + +#include +#include "gvc-mixer-stream.h" + +G_BEGIN_DECLS + +#define GVC_TYPE_MIXER_SOURCE (gvc_mixer_source_get_type ()) +#define GVC_MIXER_SOURCE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_SOURCE, GvcMixerSource)) +#define GVC_MIXER_SOURCE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_SOURCE, GvcMixerSourceClass)) +#define GVC_IS_MIXER_SOURCE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_SOURCE)) +#define GVC_IS_MIXER_SOURCE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_SOURCE)) +#define GVC_MIXER_SOURCE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_SOURCE, GvcMixerSourceClass)) + +typedef struct GvcMixerSourcePrivate GvcMixerSourcePrivate; + +typedef struct +{ + GvcMixerStream parent; + GvcMixerSourcePrivate *priv; +} GvcMixerSource; + +typedef struct +{ + GvcMixerStreamClass parent_class; +} GvcMixerSourceClass; + +GType gvc_mixer_source_get_type (void); + +GvcMixerStream * gvc_mixer_source_new (pa_context *context, + guint index, + GvcChannelMap *map); + +G_END_DECLS + +#endif /* __GVC_MIXER_SOURCE_H */ diff --git a/src/gvc/gvc-mixer-stream-private.h b/src/gvc/gvc-mixer-stream-private.h new file mode 100644 index 000000000..b97ecf5e1 --- /dev/null +++ b/src/gvc/gvc-mixer-stream-private.h @@ -0,0 +1,34 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __GVC_MIXER_STREAM_PRIVATE_H +#define __GVC_MIXER_STREAM_PRIVATE_H + +#include + +#include "gvc-channel-map.h" + +G_BEGIN_DECLS + +pa_context * gvc_mixer_stream_get_pa_context (GvcMixerStream *stream); + +G_END_DECLS + +#endif /* __GVC_MIXER_STREAM_PRIVATE_H */ diff --git a/src/gvc/gvc-mixer-stream.c b/src/gvc/gvc-mixer-stream.c new file mode 100644 index 000000000..3b4953aa0 --- /dev/null +++ b/src/gvc/gvc-mixer-stream.c @@ -0,0 +1,944 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 William Jon McCann + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include "config.h" + +#include +#include +#include + +#include +#include + +#include + +#include "gvc-mixer-stream.h" +#include "gvc-mixer-stream-private.h" +#include "gvc-channel-map-private.h" + +#define GVC_MIXER_STREAM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_STREAM, GvcMixerStreamPrivate)) + +static guint32 stream_serial = 1; + +struct GvcMixerStreamPrivate +{ + pa_context *pa_context; + guint id; + guint index; + gint card_index; + GvcChannelMap *channel_map; + char *name; + char *description; + char *application_id; + char *icon_name; + gboolean is_muted; + gboolean can_decibel; + gboolean is_event_stream; + gboolean is_virtual; + pa_volume_t base_volume; + pa_operation *change_volume_op; + char *port; + char *human_port; + GList *ports; +}; + +enum +{ + PROP_0, + PROP_ID, + PROP_PA_CONTEXT, + PROP_CHANNEL_MAP, + PROP_INDEX, + PROP_NAME, + PROP_DESCRIPTION, + PROP_APPLICATION_ID, + PROP_ICON_NAME, + PROP_VOLUME, + PROP_DECIBEL, + PROP_IS_MUTED, + PROP_CAN_DECIBEL, + PROP_IS_EVENT_STREAM, + PROP_IS_VIRTUAL, + PROP_CARD_INDEX, + PROP_PORT, +}; + +static void gvc_mixer_stream_class_init (GvcMixerStreamClass *klass); +static void gvc_mixer_stream_init (GvcMixerStream *mixer_stream); +static void gvc_mixer_stream_finalize (GObject *object); + +G_DEFINE_ABSTRACT_TYPE (GvcMixerStream, gvc_mixer_stream, G_TYPE_OBJECT) + +static guint32 +get_next_stream_serial (void) +{ + guint32 serial; + + serial = stream_serial++; + + if ((gint32)stream_serial < 0) { + stream_serial = 1; + } + + return serial; +} + +pa_context * +gvc_mixer_stream_get_pa_context (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), 0); + return stream->priv->pa_context; +} + +guint +gvc_mixer_stream_get_index (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), 0); + return stream->priv->index; +} + +guint +gvc_mixer_stream_get_id (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), 0); + return stream->priv->id; +} + +const GvcChannelMap * +gvc_mixer_stream_get_channel_map (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), NULL); + return stream->priv->channel_map; +} + +/** + * gvc_mixer_stream_get_volume: + * + * @stream: + * + * Returns: (type guint32) (transfer none): + */ +pa_volume_t +gvc_mixer_stream_get_volume (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), 0); + + return (pa_volume_t) gvc_channel_map_get_volume(stream->priv->channel_map)[VOLUME]; +} + +gdouble +gvc_mixer_stream_get_decibel (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), 0); + + return pa_sw_volume_to_dB( + (pa_volume_t) gvc_channel_map_get_volume(stream->priv->channel_map)[VOLUME]); +} + +/** + * gvc_mixer_stream_set_volume: + * + * @stream: + * @volume: (type guint32): + * + * Returns: + */ +gboolean +gvc_mixer_stream_set_volume (GvcMixerStream *stream, + pa_volume_t volume) +{ + pa_cvolume cv; + + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + cv = *gvc_channel_map_get_cvolume(stream->priv->channel_map); + pa_cvolume_scale(&cv, volume); + + if (!pa_cvolume_equal(gvc_channel_map_get_cvolume(stream->priv->channel_map), &cv)) { + gvc_channel_map_volume_changed(stream->priv->channel_map, &cv, FALSE); + g_object_notify (G_OBJECT (stream), "volume"); + return TRUE; + } + + return FALSE; +} + +gboolean +gvc_mixer_stream_set_decibel (GvcMixerStream *stream, + gdouble db) +{ + pa_cvolume cv; + + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + cv = *gvc_channel_map_get_cvolume(stream->priv->channel_map); + pa_cvolume_scale(&cv, pa_sw_volume_from_dB(db)); + + if (!pa_cvolume_equal(gvc_channel_map_get_cvolume(stream->priv->channel_map), &cv)) { + gvc_channel_map_volume_changed(stream->priv->channel_map, &cv, FALSE); + g_object_notify (G_OBJECT (stream), "volume"); + } + + return TRUE; +} + +gboolean +gvc_mixer_stream_get_is_muted (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + return stream->priv->is_muted; +} + +gboolean +gvc_mixer_stream_get_can_decibel (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + return stream->priv->can_decibel; +} + +gboolean +gvc_mixer_stream_set_is_muted (GvcMixerStream *stream, + gboolean is_muted) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + if (is_muted != stream->priv->is_muted) { + stream->priv->is_muted = is_muted; + g_object_notify (G_OBJECT (stream), "is-muted"); + } + + return TRUE; +} + +gboolean +gvc_mixer_stream_set_can_decibel (GvcMixerStream *stream, + gboolean can_decibel) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + if (can_decibel != stream->priv->can_decibel) { + stream->priv->can_decibel = can_decibel; + g_object_notify (G_OBJECT (stream), "can-decibel"); + } + + return TRUE; +} + +const char * +gvc_mixer_stream_get_name (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), NULL); + return stream->priv->name; +} + +const char * +gvc_mixer_stream_get_description (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), NULL); + return stream->priv->description; +} + +gboolean +gvc_mixer_stream_set_name (GvcMixerStream *stream, + const char *name) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + g_free (stream->priv->name); + stream->priv->name = g_strdup (name); + g_object_notify (G_OBJECT (stream), "name"); + + return TRUE; +} + +gboolean +gvc_mixer_stream_set_description (GvcMixerStream *stream, + const char *description) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + g_free (stream->priv->description); + stream->priv->description = g_strdup (description); + g_object_notify (G_OBJECT (stream), "description"); + + return TRUE; +} + +gboolean +gvc_mixer_stream_is_event_stream (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + return stream->priv->is_event_stream; +} + +gboolean +gvc_mixer_stream_set_is_event_stream (GvcMixerStream *stream, + gboolean is_event_stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + stream->priv->is_event_stream = is_event_stream; + g_object_notify (G_OBJECT (stream), "is-event-stream"); + + return TRUE; +} + +gboolean +gvc_mixer_stream_is_virtual (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + return stream->priv->is_virtual; +} + +gboolean +gvc_mixer_stream_set_is_virtual (GvcMixerStream *stream, + gboolean is_virtual) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + stream->priv->is_virtual = is_virtual; + g_object_notify (G_OBJECT (stream), "is-virtual"); + + return TRUE; +} + +const char * +gvc_mixer_stream_get_application_id (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), NULL); + return stream->priv->application_id; +} + +gboolean +gvc_mixer_stream_set_application_id (GvcMixerStream *stream, + const char *application_id) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + g_free (stream->priv->application_id); + stream->priv->application_id = g_strdup (application_id); + g_object_notify (G_OBJECT (stream), "application-id"); + + return TRUE; +} + +static void +on_channel_map_volume_changed (GvcChannelMap *channel_map, + gboolean set, + GvcMixerStream *stream) +{ + if (set == TRUE) + gvc_mixer_stream_push_volume (stream); + + g_object_notify (G_OBJECT (stream), "volume"); +} + +static gboolean +gvc_mixer_stream_set_channel_map (GvcMixerStream *stream, + GvcChannelMap *channel_map) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + if (channel_map != NULL) { + g_object_ref (channel_map); + } + + if (stream->priv->channel_map != NULL) { + g_signal_handlers_disconnect_by_func (stream->priv->channel_map, + on_channel_map_volume_changed, + stream); + g_object_unref (stream->priv->channel_map); + } + + stream->priv->channel_map = channel_map; + + if (stream->priv->channel_map != NULL) { + g_signal_connect (stream->priv->channel_map, + "volume-changed", + G_CALLBACK (on_channel_map_volume_changed), + stream); + + g_object_notify (G_OBJECT (stream), "channel-map"); + } + + return TRUE; +} + +const char * +gvc_mixer_stream_get_icon_name (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), NULL); + return stream->priv->icon_name; +} + +gboolean +gvc_mixer_stream_set_icon_name (GvcMixerStream *stream, + const char *icon_name) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + g_free (stream->priv->icon_name); + stream->priv->icon_name = g_strdup (icon_name); + g_object_notify (G_OBJECT (stream), "icon-name"); + + return TRUE; +} + +/** + * gvc_mixer_stream_get_base_volume: + * + * @stream: + * + * Returns: (type guint32) (transfer none): + */ +pa_volume_t +gvc_mixer_stream_get_base_volume (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), 0); + + return stream->priv->base_volume; +} + +/** + * gvc_mixer_stream_set_base_volume: + * + * @stream: + * @base_volume: (type guint32): + * + * Returns: + */ +gboolean +gvc_mixer_stream_set_base_volume (GvcMixerStream *stream, + pa_volume_t base_volume) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + stream->priv->base_volume = base_volume; + + return TRUE; +} + +const GvcMixerStreamPort * +gvc_mixer_stream_get_port (GvcMixerStream *stream) +{ + GList *l; + + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), NULL); + g_return_val_if_fail (stream->priv->ports != NULL, NULL); + + for (l = stream->priv->ports; l != NULL; l = l->next) { + GvcMixerStreamPort *p = l->data; + if (g_strcmp0 (stream->priv->port, p->port) == 0) { + return p; + } + } + + g_assert_not_reached (); + + return NULL; +} + +gboolean +gvc_mixer_stream_set_port (GvcMixerStream *stream, + const char *port) +{ + GList *l; + + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + g_return_val_if_fail (stream->priv->ports != NULL, FALSE); + + g_free (stream->priv->port); + stream->priv->port = g_strdup (port); + + g_free (stream->priv->human_port); + stream->priv->human_port = NULL; + + for (l = stream->priv->ports; l != NULL; l = l->next) { + GvcMixerStreamPort *p = l->data; + if (g_str_equal (stream->priv->port, p->port)) { + stream->priv->human_port = g_strdup (p->human_port); + break; + } + } + + g_object_notify (G_OBJECT (stream), "port"); + + return TRUE; +} + +gboolean +gvc_mixer_stream_change_port (GvcMixerStream *stream, + const char *port) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + return GVC_MIXER_STREAM_GET_CLASS (stream)->change_port (stream, port); +} + +const GList * +gvc_mixer_stream_get_ports (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + return stream->priv->ports; +} + +static int +sort_ports (GvcMixerStreamPort *a, + GvcMixerStreamPort *b) +{ + if (a->priority == b->priority) + return 0; + if (a->priority > b->priority) + return 1; + return -1; +} + +gboolean +gvc_mixer_stream_set_ports (GvcMixerStream *stream, + GList *ports) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + g_return_val_if_fail (stream->priv->ports == NULL, FALSE); + + stream->priv->ports = g_list_sort (ports, (GCompareFunc) sort_ports); + + return TRUE; +} + +gint +gvc_mixer_stream_get_card_index (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), PA_INVALID_INDEX); + return stream->priv->card_index; +} + +gboolean +gvc_mixer_stream_set_card_index (GvcMixerStream *stream, + gint card_index) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + stream->priv->card_index = card_index; + g_object_notify (G_OBJECT (stream), "card-index"); + + return TRUE; +} + +static void +gvc_mixer_stream_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GvcMixerStream *self = GVC_MIXER_STREAM (object); + + switch (prop_id) { + case PROP_PA_CONTEXT: + self->priv->pa_context = g_value_get_pointer (value); + break; + case PROP_INDEX: + self->priv->index = g_value_get_ulong (value); + break; + case PROP_ID: + self->priv->id = g_value_get_ulong (value); + break; + case PROP_CHANNEL_MAP: + gvc_mixer_stream_set_channel_map (self, g_value_get_object (value)); + break; + case PROP_NAME: + gvc_mixer_stream_set_name (self, g_value_get_string (value)); + break; + case PROP_DESCRIPTION: + gvc_mixer_stream_set_description (self, g_value_get_string (value)); + break; + case PROP_APPLICATION_ID: + gvc_mixer_stream_set_application_id (self, g_value_get_string (value)); + break; + case PROP_ICON_NAME: + gvc_mixer_stream_set_icon_name (self, g_value_get_string (value)); + break; + case PROP_VOLUME: + gvc_mixer_stream_set_volume (self, g_value_get_ulong (value)); + break; + case PROP_DECIBEL: + gvc_mixer_stream_set_decibel (self, g_value_get_double (value)); + break; + case PROP_IS_MUTED: + gvc_mixer_stream_set_is_muted (self, g_value_get_boolean (value)); + break; + case PROP_IS_EVENT_STREAM: + gvc_mixer_stream_set_is_event_stream (self, g_value_get_boolean (value)); + break; + case PROP_IS_VIRTUAL: + gvc_mixer_stream_set_is_virtual (self, g_value_get_boolean (value)); + break; + case PROP_CAN_DECIBEL: + gvc_mixer_stream_set_can_decibel (self, g_value_get_boolean (value)); + break; + case PROP_PORT: + gvc_mixer_stream_set_port (self, g_value_get_string (value)); + break; + case PROP_CARD_INDEX: + self->priv->card_index = g_value_get_long (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gvc_mixer_stream_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GvcMixerStream *self = GVC_MIXER_STREAM (object); + + switch (prop_id) { + case PROP_PA_CONTEXT: + g_value_set_pointer (value, self->priv->pa_context); + break; + case PROP_INDEX: + g_value_set_ulong (value, self->priv->index); + break; + case PROP_ID: + g_value_set_ulong (value, self->priv->id); + break; + case PROP_CHANNEL_MAP: + g_value_set_object (value, self->priv->channel_map); + break; + case PROP_NAME: + g_value_set_string (value, self->priv->name); + break; + case PROP_DESCRIPTION: + g_value_set_string (value, self->priv->description); + break; + case PROP_APPLICATION_ID: + g_value_set_string (value, self->priv->application_id); + break; + case PROP_ICON_NAME: + g_value_set_string (value, self->priv->icon_name); + break; + case PROP_VOLUME: + g_value_set_ulong (value, + pa_cvolume_max(gvc_channel_map_get_cvolume(self->priv->channel_map))); + break; + case PROP_DECIBEL: + g_value_set_double (value, + pa_sw_volume_to_dB(pa_cvolume_max(gvc_channel_map_get_cvolume(self->priv->channel_map)))); + break; + case PROP_IS_MUTED: + g_value_set_boolean (value, self->priv->is_muted); + break; + case PROP_IS_EVENT_STREAM: + g_value_set_boolean (value, self->priv->is_event_stream); + break; + case PROP_IS_VIRTUAL: + g_value_set_boolean (value, self->priv->is_virtual); + break; + case PROP_CAN_DECIBEL: + g_value_set_boolean (value, self->priv->can_decibel); + break; + case PROP_PORT: + g_value_set_string (value, self->priv->port); + break; + case PROP_CARD_INDEX: + g_value_set_long (value, self->priv->card_index); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GObject * +gvc_mixer_stream_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GvcMixerStream *self; + + object = G_OBJECT_CLASS (gvc_mixer_stream_parent_class)->constructor (type, n_construct_properties, construct_params); + + self = GVC_MIXER_STREAM (object); + + self->priv->id = get_next_stream_serial (); + + return object; +} + +static gboolean +gvc_mixer_stream_real_change_port (GvcMixerStream *stream, + const char *port) +{ + return FALSE; +} + +static gboolean +gvc_mixer_stream_real_push_volume (GvcMixerStream *stream, gpointer *op) +{ + return FALSE; +} + +static gboolean +gvc_mixer_stream_real_change_is_muted (GvcMixerStream *stream, + gboolean is_muted) +{ + return FALSE; +} + +gboolean +gvc_mixer_stream_push_volume (GvcMixerStream *stream) +{ + pa_operation *op; + gboolean ret; + + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + if (stream->priv->is_event_stream != FALSE) + return TRUE; + + g_debug ("Pushing new volume to stream '%s' (%s)", + stream->priv->description, stream->priv->name); + + ret = GVC_MIXER_STREAM_GET_CLASS (stream)->push_volume (stream, (gpointer *) &op); + if (ret) { + if (stream->priv->change_volume_op != NULL) + pa_operation_unref (stream->priv->change_volume_op); + stream->priv->change_volume_op = op; + } + return ret; +} + +gboolean +gvc_mixer_stream_change_is_muted (GvcMixerStream *stream, + gboolean is_muted) +{ + gboolean ret; + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + ret = GVC_MIXER_STREAM_GET_CLASS (stream)->change_is_muted (stream, is_muted); + return ret; +} + +gboolean +gvc_mixer_stream_is_running (GvcMixerStream *stream) +{ + if (stream->priv->change_volume_op == NULL) + return FALSE; + + if ((pa_operation_get_state(stream->priv->change_volume_op) == PA_OPERATION_RUNNING)) + return TRUE; + + pa_operation_unref(stream->priv->change_volume_op); + stream->priv->change_volume_op = NULL; + + return FALSE; +} + +static void +gvc_mixer_stream_class_init (GvcMixerStreamClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->constructor = gvc_mixer_stream_constructor; + gobject_class->finalize = gvc_mixer_stream_finalize; + gobject_class->set_property = gvc_mixer_stream_set_property; + gobject_class->get_property = gvc_mixer_stream_get_property; + + klass->push_volume = gvc_mixer_stream_real_push_volume; + klass->change_port = gvc_mixer_stream_real_change_port; + klass->change_is_muted = gvc_mixer_stream_real_change_is_muted; + + g_object_class_install_property (gobject_class, + PROP_INDEX, + g_param_spec_ulong ("index", + "Index", + "The index for this stream", + 0, G_MAXULONG, 0, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (gobject_class, + PROP_ID, + g_param_spec_ulong ("id", + "id", + "The id for this stream", + 0, G_MAXULONG, 0, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (gobject_class, + PROP_CHANNEL_MAP, + g_param_spec_object ("channel-map", + "channel map", + "The channel map for this stream", + GVC_TYPE_CHANNEL_MAP, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_PA_CONTEXT, + g_param_spec_pointer ("pa-context", + "PulseAudio context", + "The PulseAudio context for this stream", + G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (gobject_class, + PROP_VOLUME, + g_param_spec_ulong ("volume", + "Volume", + "The volume for this stream", + 0, G_MAXULONG, 0, + G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, + PROP_DECIBEL, + g_param_spec_double ("decibel", + "Decibel", + "The decibel level for this stream", + -G_MAXDOUBLE, G_MAXDOUBLE, 0, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + + g_object_class_install_property (gobject_class, + PROP_NAME, + g_param_spec_string ("name", + "Name", + "Name to display for this stream", + NULL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_DESCRIPTION, + g_param_spec_string ("description", + "Description", + "Description to display for this stream", + NULL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_APPLICATION_ID, + g_param_spec_string ("application-id", + "Application identifier", + "Application identifier for this stream", + NULL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_ICON_NAME, + g_param_spec_string ("icon-name", + "Icon Name", + "Name of icon to display for this stream", + NULL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_IS_MUTED, + g_param_spec_boolean ("is-muted", + "is muted", + "Whether stream is muted", + FALSE, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_CAN_DECIBEL, + g_param_spec_boolean ("can-decibel", + "can decibel", + "Whether stream volume can be converted to decibel units", + FALSE, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_IS_EVENT_STREAM, + g_param_spec_boolean ("is-event-stream", + "is event stream", + "Whether stream's role is to play an event", + FALSE, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_IS_VIRTUAL, + g_param_spec_boolean ("is-virtual", + "is virtual stream", + "Whether the stream is virtual", + FALSE, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_PORT, + g_param_spec_string ("port", + "Port", + "The name of the current port for this stream", + NULL, + G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, + PROP_CARD_INDEX, + g_param_spec_long ("card-index", + "Card index", + "The index of the card for this stream", + PA_INVALID_INDEX, G_MAXLONG, PA_INVALID_INDEX, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_type_class_add_private (klass, sizeof (GvcMixerStreamPrivate)); +} + +static void +gvc_mixer_stream_init (GvcMixerStream *stream) +{ + stream->priv = GVC_MIXER_STREAM_GET_PRIVATE (stream); +} + +static void +free_port (GvcMixerStreamPort *p) +{ + g_free (p->port); + g_free (p->human_port); + g_free (p); +} + +static void +gvc_mixer_stream_finalize (GObject *object) +{ + GvcMixerStream *mixer_stream; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_STREAM (object)); + + mixer_stream = GVC_MIXER_STREAM (object); + + g_return_if_fail (mixer_stream->priv != NULL); + + g_object_unref (mixer_stream->priv->channel_map); + mixer_stream->priv->channel_map = NULL; + + g_free (mixer_stream->priv->name); + mixer_stream->priv->name = NULL; + + g_free (mixer_stream->priv->description); + mixer_stream->priv->description = NULL; + + g_free (mixer_stream->priv->application_id); + mixer_stream->priv->application_id = NULL; + + g_free (mixer_stream->priv->icon_name); + mixer_stream->priv->icon_name = NULL; + + g_free (mixer_stream->priv->port); + mixer_stream->priv->port = NULL; + + g_free (mixer_stream->priv->human_port); + mixer_stream->priv->human_port = NULL; + + g_list_foreach (mixer_stream->priv->ports, (GFunc) free_port, NULL); + g_list_free (mixer_stream->priv->ports); + mixer_stream->priv->ports = NULL; + + if (mixer_stream->priv->change_volume_op) { + pa_operation_unref(mixer_stream->priv->change_volume_op); + mixer_stream->priv->change_volume_op = NULL; + } + + G_OBJECT_CLASS (gvc_mixer_stream_parent_class)->finalize (object); +} diff --git a/src/gvc/gvc-mixer-stream.h b/src/gvc/gvc-mixer-stream.h new file mode 100644 index 000000000..53b7eb6d6 --- /dev/null +++ b/src/gvc/gvc-mixer-stream.h @@ -0,0 +1,125 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __GVC_MIXER_STREAM_H +#define __GVC_MIXER_STREAM_H + +#include +#include "gvc-pulseaudio-fake.h" +#include "gvc-channel-map.h" + +G_BEGIN_DECLS + +#define GVC_TYPE_MIXER_STREAM (gvc_mixer_stream_get_type ()) +#define GVC_MIXER_STREAM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_STREAM, GvcMixerStream)) +#define GVC_MIXER_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_STREAM, GvcMixerStreamClass)) +#define GVC_IS_MIXER_STREAM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_STREAM)) +#define GVC_IS_MIXER_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_STREAM)) +#define GVC_MIXER_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_STREAM, GvcMixerStreamClass)) + +typedef struct GvcMixerStreamPrivate GvcMixerStreamPrivate; + +typedef struct +{ + GObject parent; + GvcMixerStreamPrivate *priv; +} GvcMixerStream; + +typedef struct +{ + GObjectClass parent_class; + + /* vtable */ + gboolean (*push_volume) (GvcMixerStream *stream, + gpointer *operation); + gboolean (*change_is_muted) (GvcMixerStream *stream, + gboolean is_muted); + gboolean (*change_port) (GvcMixerStream *stream, + const char *port); +} GvcMixerStreamClass; + +typedef struct +{ + char *port; + char *human_port; + guint priority; +} GvcMixerStreamPort; + +GType gvc_mixer_stream_get_type (void); + +guint gvc_mixer_stream_get_index (GvcMixerStream *stream); +guint gvc_mixer_stream_get_id (GvcMixerStream *stream); +const GvcChannelMap *gvc_mixer_stream_get_channel_map(GvcMixerStream *stream); +const GvcMixerStreamPort *gvc_mixer_stream_get_port (GvcMixerStream *stream); +const GList * gvc_mixer_stream_get_ports (GvcMixerStream *stream); +gboolean gvc_mixer_stream_change_port (GvcMixerStream *stream, + const char *port); + +pa_volume_t gvc_mixer_stream_get_volume (GvcMixerStream *stream); +gdouble gvc_mixer_stream_get_decibel (GvcMixerStream *stream); +gboolean gvc_mixer_stream_push_volume (GvcMixerStream *stream); +pa_volume_t gvc_mixer_stream_get_base_volume (GvcMixerStream *stream); + +gboolean gvc_mixer_stream_get_is_muted (GvcMixerStream *stream); +gboolean gvc_mixer_stream_get_can_decibel (GvcMixerStream *stream); +gboolean gvc_mixer_stream_change_is_muted (GvcMixerStream *stream, + gboolean is_muted); +gboolean gvc_mixer_stream_is_running (GvcMixerStream *stream); +const char * gvc_mixer_stream_get_name (GvcMixerStream *stream); +const char * gvc_mixer_stream_get_icon_name (GvcMixerStream *stream); +const char * gvc_mixer_stream_get_description (GvcMixerStream *stream); +const char * gvc_mixer_stream_get_application_id (GvcMixerStream *stream); +gboolean gvc_mixer_stream_is_event_stream (GvcMixerStream *stream); +gboolean gvc_mixer_stream_is_virtual (GvcMixerStream *stream); +gint gvc_mixer_stream_get_card_index (GvcMixerStream *stream); + +/* private */ +gboolean gvc_mixer_stream_set_volume (GvcMixerStream *stream, + pa_volume_t volume); +gboolean gvc_mixer_stream_set_decibel (GvcMixerStream *stream, + gdouble db); +gboolean gvc_mixer_stream_set_is_muted (GvcMixerStream *stream, + gboolean is_muted); +gboolean gvc_mixer_stream_set_can_decibel (GvcMixerStream *stream, + gboolean can_decibel); +gboolean gvc_mixer_stream_set_name (GvcMixerStream *stream, + const char *name); +gboolean gvc_mixer_stream_set_description (GvcMixerStream *stream, + const char *description); +gboolean gvc_mixer_stream_set_icon_name (GvcMixerStream *stream, + const char *name); +gboolean gvc_mixer_stream_set_is_event_stream (GvcMixerStream *stream, + gboolean is_event_stream); +gboolean gvc_mixer_stream_set_is_virtual (GvcMixerStream *stream, + gboolean is_event_stream); +gboolean gvc_mixer_stream_set_application_id (GvcMixerStream *stream, + const char *application_id); +gboolean gvc_mixer_stream_set_base_volume (GvcMixerStream *stream, + pa_volume_t base_volume); +gboolean gvc_mixer_stream_set_port (GvcMixerStream *stream, + const char *port); +gboolean gvc_mixer_stream_set_ports (GvcMixerStream *stream, + GList *ports); +gboolean gvc_mixer_stream_set_card_index (GvcMixerStream *stream, + gint card_index); + +G_END_DECLS + +#endif /* __GVC_MIXER_STREAM_H */ diff --git a/src/gvc/gvc-pulseaudio-fake.h b/src/gvc/gvc-pulseaudio-fake.h new file mode 100644 index 000000000..65293cdef --- /dev/null +++ b/src/gvc/gvc-pulseaudio-fake.h @@ -0,0 +1,34 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __GVC_PULSEAUDIO_FAKE_H +#define __GVC_PULSEAUDIO_FAKE_H + +#ifdef WITH_INTROSPECTION + +#ifndef PA_API_VERSION +typedef int pa_channel_position_t; +typedef guint32 pa_volume_t; +typedef gpointer pa_context; +#endif /* PA_API_VERSION */ + +#endif /* WITH_INTROSPECTION */ + +#endif /* __GVC_PULSEAUDIO_FAKE_H */ diff --git a/src/shell-global.c b/src/shell-global.c index 5251380d1..1e42fc9f0 100644 --- a/src/shell-global.c +++ b/src/shell-global.c @@ -24,6 +24,7 @@ #include #include #include +#include #ifdef HAVE_SYS_RESOURCE_H #include #endif @@ -67,6 +68,9 @@ struct _ShellGlobal { guint work_count; GSList *leisure_closures; guint leisure_function_id; + + /* For sound notifications */ + ca_context *sound_context; }; enum { @@ -214,6 +218,10 @@ shell_global_init (ShellGlobal *global) global->last_change_screen_width = 0; global->last_change_screen_height = 0; + + ca_context_create (&global->sound_context); + ca_context_change_props (global->sound_context, CA_PROP_APPLICATION_NAME, PACKAGE_NAME, CA_PROP_APPLICATION_ID, "org.gnome.Shell", NULL); + ca_context_open (global->sound_context); } static void @@ -1788,3 +1796,18 @@ shell_global_run_at_leisure (ShellGlobal *global, if (global->work_count == 0) schedule_leisure_functions (global); } + +/** + * shell_global_play_theme_sound: + * @global: the #ShellGlobal + * @name: the sound name + * + * Plays a simple sound picked according to Freedesktop sound theme. + * Really just a workaround for libcanberra not being introspected. + */ +void +shell_global_play_theme_sound (ShellGlobal *global, + const char *name) +{ + ca_context_play (global->sound_context, 0, CA_PROP_EVENT_ID, name, NULL); +} diff --git a/src/shell-global.h b/src/shell-global.h index 34c56786b..5cf5e1373 100644 --- a/src/shell-global.h +++ b/src/shell-global.h @@ -130,6 +130,9 @@ void shell_global_run_at_leisure (ShellGlobal *global, gpointer user_data, GDestroyNotify notify); +void shell_global_play_theme_sound (ShellGlobal *global, + const char *name); + G_END_DECLS #endif /* __SHELL_GLOBAL_H__ */ diff --git a/src/st/st-drawing-area.c b/src/st/st-drawing-area.c index fa99d11e6..a10da15db 100644 --- a/src/st/st-drawing-area.c +++ b/src/st/st-drawing-area.c @@ -115,6 +115,7 @@ st_drawing_area_paint (ClutterActor *self) surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); priv->context = cairo_create (surface); priv->in_repaint = TRUE; + priv->needs_repaint = FALSE; g_signal_emit ((GObject*)area, st_drawing_area_signals[REPAINT], 0); diff --git a/src/st/st-theme.c b/src/st/st-theme.c index 4134b758b..6e2fd2c74 100644 --- a/src/st/st-theme.c +++ b/src/st/st-theme.c @@ -320,6 +320,7 @@ st_theme_load_stylesheet (StTheme *theme, return FALSE; insert_stylesheet (theme, path, stylesheet); + cr_stylesheet_ref (stylesheet); theme->custom_stylesheets = g_slist_prepend (theme->custom_stylesheets, stylesheet); return TRUE; diff --git a/tools/build/gnome-shell-build-setup.sh b/tools/build/gnome-shell-build-setup.sh index 35fa92a8c..ca0bbf9cf 100755 --- a/tools/build/gnome-shell-build-setup.sh +++ b/tools/build/gnome-shell-build-setup.sh @@ -58,7 +58,7 @@ fi # # Devel packages needed by gnome-shell and its deps: # dbus-glib, GL, gnome-menus, gstreamer, libffi, -# libjasper, libjpeg, libpng, libtiff, libwnck, +# libjasper, libjpeg, libpng, libpulse, libtiff, libwnck, # libxml2, ORBit2, python, readline, # spidermonkey ({mozilla,firefox,xulrunner}-js), startup-notification # xdamage, icon-naming-utils @@ -83,7 +83,7 @@ if test "x$system" = xUbuntu -o "x$system" = xDebian -o "x$system" = xLinuxMint gvfs gvfs-backends icon-naming-utils \ libdbus-glib-1-dev libffi-dev libgnome-menu-dev libgnome-desktop-dev \ libjasper-dev libjpeg-dev libpng-dev libstartup-notification0-dev libtiff-dev \ - libwnck-dev libgl1-mesa-dev liborbit2-dev libreadline5-dev libxml2-dev \ + libwnck-dev libgl1-mesa-dev liborbit2-dev libpulse-dev libreadline5-dev libxml2-dev \ mesa-common-dev mesa-utils python-dev python-gconf python-gobject \ xulrunner-dev xserver-xephyr gnome-terminal libcroco3-dev \ libgstreamer0.10-dev gstreamer0.10-plugins-base gstreamer0.10-plugins-good \ @@ -105,8 +105,8 @@ if test "x$system" = xFedora ; then automake bison flex gettext git gnome-common gnome-doc-utils gvfs intltool libtool pkgconfig dbus-glib-devel gnome-desktop-devel gnome-menus-devel gnome-python2-gconf jasper-devel libffi-devel libjpeg-devel libpng-devel - libtiff-devel libwnck-devel mesa-libGL-devel ORBit2-devel python-devel - pygobject2 readline-devel xulrunner-devel libXdamage-devel libcroco-devel + libtiff-devel libwnck-devel mesa-libGL-devel ORBit2-devel pulseaudio-libs-devel + python-devel pygobject2 readline-devel xulrunner-devel libXdamage-devel libcroco-devel libxml2-devel gstreamer-devel gstreamer-plugins-base gstreamer-plugins-good glx-utils startup-notification-devel xorg-x11-server-Xephyr gnome-terminal zenity icon-naming-utils @@ -132,7 +132,7 @@ if test "x$system" = xSUSE -o "x$system" = "xSUSE LINUX" ; then curl \ bison flex gtk-doc gnome-common gnome-doc-utils-devel \ gnome-desktop-devel gnome-menus-devel icon-naming-utils \ - libtiff-devel cups-devel libffi-devel \ + libpulse-devel libtiff-devel cups-devel libffi-devel \ orbit2-devel libwnck-devel xorg-x11-proto-devel readline-devel \ mozilla-xulrunner191-devel libcroco-devel \ xorg-x11-devel xorg-x11 xorg-x11-server-extra \