22import '@girs/gjs' ;
33
44import St from '@girs/st-17' ;
5+ import Gio from '@girs/gio-2.0' ;
56
67import type { QuickSlider } from '@girs/gnome-shell/ui/quickSettings' ;
78import * as Main from '@girs/gnome-shell/ui/main' ;
89import * as PopupMenu from '@girs/gnome-shell/ui/popupMenu' ;
9-
1010import { Module } from '~/module.ts' ;
1111import { VolumeMixerPanel } from '~/modules/volumeMixer/mixerPanel.ts' ;
12+ import { loadIcon } from '~/shared/icons.ts' ;
1213
1314/**
1415 * Volume Mixer Module
1516 *
1617 * Adds a toggle button beside the output slider's device-list icon in Quick
17- * Settings. Clicking it opens the slider menu with an additional section that
18- * shows per-application volume sliders.
18+ * Settings. Clicking it opens the slider menu with the following layout:
19+ *
20+ * [header] "Volume Mixer"
21+ * [_menuSection] per-application sliders ← before separator
22+ * [separator] ─────────────────────────
23+ * [_settingsSection] "Sound Settings" link ← after separator
24+ *
25+ * OutputStreamSlider already calls setHeader() in its own _init(), so by the
26+ * time _attachToSlider() runs the menu already contains:
27+ * header(0) · separator(1) · deviceSection(2) · …
28+ * We insert _menuSection at 1 (shifting separator to 2) and _settingsSection
29+ * at 3 (between separator and deviceSection).
1930 */
2031export class VolumeMixer extends Module {
2132 private _panel : InstanceType < typeof VolumeMixerPanel > | null = null ;
2233 private _toggleButton : St . Button | null = null ;
2334 private _menuSection : InstanceType < typeof PopupMenu . PopupMenuSection > | null =
2435 null ;
36+ private _settingsSection : InstanceType < typeof PopupMenu . PopupMenuSection > | null =
37+ null ;
2538 private _outputSlider : QuickSlider | null = null ;
2639 private _menuClosedId = 0 ;
2740 private _gridChildAddedId = 0 ;
41+ private _quickSettings : Main . QuickSettings | null = null ;
2842
2943 override enable ( ) : void {
44+ this . _quickSettings = Main . panel . statusArea . quickSettings ;
45+
3046 const outputSlider = this . _findOutputSlider ( ) ;
3147 if ( outputSlider ) {
3248 this . _attachToSlider ( outputSlider ) ;
@@ -71,6 +87,11 @@ export class VolumeMixer extends Module {
7187 this . _menuSection = null ;
7288 }
7389
90+ if ( this . _settingsSection ) {
91+ this . _settingsSection . destroy ( ) ;
92+ this . _settingsSection = null ;
93+ }
94+
7495 if ( this . _panel ) {
7596 this . _panel . destroy ( ) ;
7697 this . _panel = null ;
@@ -80,8 +101,12 @@ export class VolumeMixer extends Module {
80101 }
81102
82103 private _findOutputSlider ( ) : QuickSlider | null {
83- const quickSettings = Main . panel . statusArea . quickSettings ;
84- const grid = quickSettings . menu . _grid ;
104+ const grid = this . _quickSettings ?. menu ?. _grid ;
105+
106+ if ( ! grid ) {
107+ console . error ( 'Aurora Shell: VolumeMixer could not find quick settings grid' ) ;
108+ return null ;
109+ }
85110
86111 for ( const child of grid . get_children ( ) ) {
87112 if ( child . constructor . name === 'OutputStreamSlider' ) {
@@ -92,22 +117,36 @@ export class VolumeMixer extends Module {
92117 return null ;
93118 }
94119
95- /**
96- * Attaches the volume mixer toggle button and panel to the output stream
97- * slider's popup menu. Clicking the button opens a menu showing only
98- * per-application volume sliders.
99- */
100120 private _attachToSlider ( slider : QuickSlider ) : void {
101121 this . _outputSlider = slider ;
102122 this . _panel = new VolumeMixerPanel ( ) ;
103123 this . _menuSection = new PopupMenu . PopupMenuSection ( ) ;
124+
104125 this . _menuSection . box . add_child ( this . _panel ) ;
126+ slider . menu . addMenuItem ( this . _menuSection , 1 ) ;
105127 this . _menuSection . box . hide ( ) ;
106128
107- slider . menu . addMenuItem ( this . _menuSection , 2 ) ;
129+ this . _settingsSection = new PopupMenu . PopupMenuSection ( ) ;
130+ const settingsItem = new PopupMenu . PopupMenuItem (
131+ _ ( 'Sound Settings' ) ,
132+ ) ;
133+ settingsItem . connect ( 'activate' , ( ) => {
134+ try {
135+ Gio . Subprocess . new (
136+ [ 'gnome-control-center' , 'sound' ] ,
137+ Gio . SubprocessFlags . NONE ,
138+ ) ;
139+ } catch ( e ) {
140+ console . error ( `Aurora Shell: Failed to open sound settings: ${ e } ` ) ;
141+ }
142+ this . _quickSettings ?. menu . close ( true ) ;
143+ } ) ;
144+ this . _settingsSection . addMenuItem ( settingsItem ) ;
145+ slider . menu . addMenuItem ( this . _settingsSection , 3 ) ;
146+ this . _settingsSection . box . hide ( ) ;
108147
109148 this . _toggleButton = new St . Button ( {
110- child : new St . Icon ( { icon_name : 'open-menu -symbolic' } ) ,
149+ child : new St . Icon ( { gicon : loadIcon ( 'volume-mixer -symbolic') } ) ,
111150 style_class : 'icon-button flat' ,
112151 can_focus : true ,
113152 x_expand : false ,
@@ -118,18 +157,20 @@ export class VolumeMixer extends Module {
118157 slider . child . add_child ( this . _toggleButton ) ;
119158
120159 this . _toggleButton . connect ( 'clicked' , ( ) => {
121- if ( ! this . _panel || ! this . _menuSection ) return ;
160+ if ( ! this . _panel || ! this . _menuSection || ! this . _settingsSection ) return ;
122161
123162 this . _menuSection . box . show ( ) ;
163+ this . _settingsSection . box . show ( ) ;
124164 slider . _deviceSection ?. box . hide ( ) ;
125165 slider . menu . _setSettingsVisibility ?.( false ) ;
126166 slider . menu . setHeader ( 'audio-speakers-symbolic' , _ ( 'Volume Mixer' ) ) ;
127167 slider . menu . open ( true ) ;
128168 } ) ;
129169
130170 this . _menuClosedId = slider . menu . connect ( 'menu-closed' , ( ) => {
131- if ( ! this . _menuSection ) return ;
171+ if ( ! this . _menuSection || ! this . _settingsSection ) return ;
132172 this . _menuSection . box . hide ( ) ;
173+ this . _settingsSection . box . hide ( ) ;
133174 slider . _deviceSection ?. box . show ( ) ;
134175 slider . menu . _setSettingsVisibility ?.( Main . sessionMode . allowSettings ) ;
135176 slider . menu . setHeader ( 'audio-headphones-symbolic' , _ ( 'Sound Output' ) ) ;
0 commit comments