2 // Mixer.app / AlsaMixer.app
4 // Copyright (c) 1998-2002 Per Liden
5 // Copyright (C) 2004, Petr Hlavka
7 // This program is free software; you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License as published by
9 // the Free Software Foundation; either version 2 of the License, or
10 // (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1307,
32 #include "AMixer/AMixer.h"
34 #include "pixmaps/main.xpm"
35 #include "pixmaps/button.xpm"
36 #include "pixmaps/mutebutton.xpm"
37 #include "pixmaps/redlight.xpm"
39 #define ROUND_POS(x) (int ((x) + 0.5) > int (x)) ? (int) ((x) + 1) : (int) (x)
43 static const int ButtonX
[] = {6, 24, 42};
44 static const char* MixerSources
[] = { "Master", "PCM", "CD" };
48 void catchBrokenPipe(int sig
)
50 app
->saveVolumeSettings();
54 int positionToPercent(int position
) {
55 return ROUND_POS(100 - (((position
- BUTTON_MAX
) * 100.0) / (BUTTON_MIN
- BUTTON_MAX
)));
58 int percentToPosition(int percent
) {
59 return ROUND_POS(BUTTON_MIN
- (percent
* (BUTTON_MIN
- BUTTON_MAX
)) / 100.0);
62 Mixer::Mixer(int argc
, char** argv
)
69 char* displayName
= NULL
;
70 char* card
= "default";
73 mInstanceName
= INSTANCENAME
;
74 mVolumeSource
[0] = -1;
75 mVolumeSource
[1] = -1;
76 mVolumeSource
[2] = -1;
83 mSaveSettings
= false;
84 mLoadSettings
= false;
89 for (int i
=1; i
<argc
; i
++) {
91 if (!strcmp(argv
[i
], "-d")) {
92 checkArgument(argv
, argc
, i
);
93 displayName
= argv
[i
+1];
98 else if (!strcmp(argv
[i
], "-1") || !strcmp(argv
[i
], "-2") || !strcmp(argv
[i
], "-3")) {
99 checkArgument(argv
, argc
, i
);
100 MixerSources
[argv
[i
][1] - '1'] = argv
[i
+ 1];
105 else if (!strcmp(argv
[i
], "-w")) {
106 checkArgument(argv
, argc
, i
);
107 mWheelButton
= atoi(argv
[i
+1]);
109 if (mWheelButton
< 1 || mWheelButton
> 3) {
110 cerr
<< APPNAME
<< ": invalid wheel binding, must be 1, 2 or 3, not " << argv
[i
+1] << endl
;
119 else if (!strcmp(argv
[i
], "-l")) {
120 checkArgument(argv
, argc
, i
);
121 mLabelText
= argv
[i
+1];
125 // Save settings on exit
126 else if (!strcmp(argv
[i
], "-S")) {
127 mSaveSettings
= true;
130 // Load settings on startup
131 else if (!strcmp(argv
[i
], "-L")) {
132 mLoadSettings
= true;
135 // Load/Save settings file
136 else if (!strcmp(argv
[i
], "-f")) {
137 checkArgument(argv
, argc
, i
);
138 mSettingsFile
= argv
[i
+1];
142 // Execute command on middle click
143 else if (!strcmp(argv
[i
], "-e")) {
144 checkArgument(argv
, argc
, i
);
145 mCommand
= argv
[i
+ 1];
150 else if (!strcmp(argv
[i
], "-n")) {
151 checkArgument(argv
, argc
, i
);
152 mInstanceName
= argv
[i
+1];
157 else if (!strcmp(argv
[i
], "-v")) {
158 cerr
<< APPNAME
<< " version " << VERSION
<< endl
;
163 else if (!strcmp(argv
[i
], "-h") || !strcmp(argv
[i
], "--help")) {
169 else if (!strcmp(argv
[i
], "--card")) {
170 card
= AMixer::convertIDToCard(argv
[i
+ 1]);
172 cerr
<< APPNAME
<< ": invalid card number '" << argv
[i
+ 1] << "'" << endl
;
180 else if (!strcmp(argv
[i
], "--device")) {
187 cerr
<< APPNAME
<< ": invalid option '" << argv
[i
] << "'" << endl
;
194 // default settings file
195 if (!mSettingsFile
) {
196 char* home
= getenv("HOME");
198 mSettingsFile
= new char[strlen(home
) + strlen(SETTINGS
) + 1];
199 strcpy(mSettingsFile
, home
);
200 strcat(mSettingsFile
, SETTINGS
);
202 cerr
<< APPNAME
<< ": $HOME not set, could not find saved settings" << endl
;
207 aMixer
= new AMixer(card
);
208 if (!aMixer
->opened()) {
209 cerr
<< APPNAME
<< ": could not open mixer device for card '" << card
<< "'" << endl
;
213 // open mixer sources
214 for (int i
= 0; i
< 3; i
++) {
215 aMixer
->attachItem(i
, MixerSources
[i
]);
216 if (!aMixer
->itemOK(i
))
217 cerr
<< APPNAME
<< ": could not select mixer source '" << MixerSources
[i
] << "'" << endl
;
221 if ((mDisplay
= XOpenDisplay(displayName
)) == NULL
) {
222 cerr
<< APPNAME
<< ": could not open display " << displayName
<< endl
;
227 mRoot
= RootWindow(mDisplay
, DefaultScreen(mDisplay
));
230 mAppWin
= XCreateSimpleWindow(mDisplay
, mRoot
, 1, 1, 64, 64, 0, 0, 0);
231 mIconWin
= XCreateSimpleWindow(mDisplay
, mAppWin
, 0, 0, 64, 64, 0, 0, 0);
234 classHint
.res_name
= mInstanceName
;
235 classHint
.res_class
= CLASSNAME
;
236 XSetClassHint(mDisplay
, mAppWin
, &classHint
);
238 // Create delete atom
239 deleteWindow
= XInternAtom(mDisplay
, "WM_DELETE_WINDOW", False
);
240 XSetWMProtocols(mDisplay
, mAppWin
, &deleteWindow
, 1);
241 XSetWMProtocols(mDisplay
, mIconWin
, &deleteWindow
, 1);
244 XStoreName(mDisplay
, mAppWin
, APPNAME
);
245 XSetIconName(mDisplay
, mAppWin
, APPNAME
);
248 sizeHints
.flags
= USPosition
;
251 XSetWMNormalHints(mDisplay
, mAppWin
, &sizeHints
);
254 wmHints
.initial_state
= WithdrawnState
;
255 wmHints
.icon_window
= mIconWin
;
258 wmHints
.window_group
= mAppWin
;
259 wmHints
.flags
= StateHint
| IconWindowHint
| IconPositionHint
| WindowGroupHint
;
260 XSetWMHints(mDisplay
, mAppWin
, &wmHints
);
263 XSetCommand(mDisplay
, mAppWin
, argv
, argc
);
265 // Set background image
266 image
= new Xpm(mDisplay
, mRoot
, main_xpm
);
268 image
->drawString(LABEL_X
, LABEL_Y
, mLabelText
);
270 image
->setWindowPixmapShaped(mIconWin
);
274 mButton
[0] = XCreateSimpleWindow(mDisplay
, mIconWin
, ButtonX
[0], BUTTON_MIN
, 5, 5, 0, 0, 0);
275 mButton
[1] = XCreateSimpleWindow(mDisplay
, mIconWin
, ButtonX
[1], BUTTON_MIN
, 5, 5, 0, 0, 0);
276 mButton
[2] = XCreateSimpleWindow(mDisplay
, mIconWin
, ButtonX
[2], BUTTON_MIN
, 5, 5, 0, 0, 0);
278 image
= new Xpm(mDisplay
, mRoot
, button_xpm
);
279 image
->setWindowPixmap(mButton
[0]);
280 image
->setWindowPixmap(mButton
[1]);
281 image
->setWindowPixmap(mButton
[2]);
284 XSelectInput(mDisplay
, mButton
[0], ButtonPressMask
| ButtonReleaseMask
| PointerMotionMask
);
285 XSelectInput(mDisplay
, mButton
[1], ButtonPressMask
| ButtonReleaseMask
| PointerMotionMask
);
286 XSelectInput(mDisplay
, mButton
[2], ButtonPressMask
| ButtonReleaseMask
| PointerMotionMask
);
287 XSelectInput(mDisplay
, mIconWin
, ButtonPressMask
);
289 XMapWindow(mDisplay
, mButton
[0]);
290 XMapWindow(mDisplay
, mButton
[1]);
291 XMapWindow(mDisplay
, mButton
[2]);
293 XMapWindow(mDisplay
, mIconWin
);
294 XMapWindow(mDisplay
, mAppWin
);
295 XSync(mDisplay
, False
);
297 // Catch broker pipe signal
298 signal(SIGPIPE
, catchBrokenPipe
);
306 loadVolumeSettings();
310 void Mixer::tryHelp(char* appname
)
312 cerr
<< "Try `" << appname
<< " --help' for more information" << endl
;
315 void Mixer::showHelp()
317 cerr
<< APPNAME
<< " Copyright (c) 1998-2002 by Per Liden (per@fukt.bth.se), Petr Hlavka (xhlavk00@stud.fit.vutbr.cz)" << endl
<< endl
318 << "options:" << endl
319 << " -1 <source> set sound source for control 1 (default is Master)" << endl
320 << " -2 <source> set sound source for control 2 (default is PCM)" << endl
321 << " -3 <source> set sound source for control 3 (default is CD)" << endl
322 << " -w 1|2|3 bind a control button to the mouse wheel (default is 1)" << endl
323 << " -l <text> set label text" << endl
324 << " -S save volume settings on exit" << endl
325 << " -L load volume settings on start up" << endl
326 << " -f <file> use setting <file> instead of ~/GNUstep/Defaults/AlsaMixer" << endl
327 << " --card <id> select card" << endl
328 << " --device <dev> select device, default 'default'" << endl
329 << " -e <command> execute <command> on middle click" << endl
330 << " -n <name> set client instance name" << endl
331 << " -d <disp> set display" << endl
332 << " -v print version and exit" << endl
333 << " -h, --help display this help and exit" << endl
<< endl
;
336 void Mixer::checkArgument(char** argv
, int argc
, int index
)
338 if (argc
-1 < index
+1) {
339 cerr
<< APPNAME
<< ": option '" << argv
[index
] << "' requires an argument" << endl
;
345 void Mixer::showErrorLed()
350 led
= XCreateSimpleWindow(mDisplay
, mIconWin
, LED_X
, LED_Y
, 3, 2, 0, 0, 0);
352 // Set background image
353 image
= new Xpm(mDisplay
, mRoot
, redlight_xpm
);
354 image
->setWindowPixmap(led
);
358 XMapWindow(mDisplay
, led
);
362 void Mixer::loadVolumeSettings()
365 ifstream
file(mSettingsFile
);
367 // This could fail if the user has edited the file by hand and destroyed the structure
370 file
>> dummy
; // Volume1
375 file
>> dummy
; // Volume2
380 file
>> dummy
; // Volume3
385 for (int i
= 0; i
< 3; i
++) {
386 setVolume(i
, mVolume
[i
]);
387 setButtonPosition(i
, percentToPosition(mVolume
[i
]));
393 void Mixer::saveVolumeSettings()
396 ofstream
file(mSettingsFile
);
398 // Files in ~/GNUstep/Defaults/ should follow the property list format
400 << " Volume1 = " << mVolumePos
[0] << ";" << endl
401 << " Volume2 = " << mVolumePos
[1] << ";" << endl
402 << " Volume3 = " << mVolumePos
[2] << ";" << endl
406 cerr
<< APPNAME
<< ": failed to save volume settings in " << mSettingsFile
<< endl
;
411 void Mixer::getVolume()
413 static int lastVolume
[3] = {-1, -1, -1};
414 static int lastVolumeMute
[3] = {-1, -1, -1};
421 for (int i
=0; i
<3; i
++) {
422 mVolume
[i
] = aMixer
->itemGetVolume(i
);
423 mVolumeMute
[i
] = aMixer
->itemIsMuted(i
);
425 if (lastVolume
[i
] != mVolume
[i
]) {
428 // Set button position
432 y
= percentToPosition(mVolume
[i
]);
435 setButtonPosition(i
, y
);
436 lastVolume
[i
] = mVolume
[i
];
439 // set buttom type muted/unmuted
440 if (lastVolumeMute
[i
] != mVolumeMute
[i
]) {
442 lastVolumeMute
[i
] = mVolumeMute
[i
];
447 cerr
<< APPNAME
<< ": unable to read from " << mMixerDevice
<< endl
;
453 void Mixer::setVolume(int button
, int volume
)
460 mVolume
[button
] = volume
;
463 aMixer
->itemSetVolume(button
, mVolume
[button
]);
466 void Mixer::toggleMute(int button
)
468 aMixer
->itemToggleMute(button
);
469 mVolumeMute
[button
] = aMixer
->itemIsMuted(button
);
470 setButtonType(button
);
473 void Mixer::setButtonType(int button
)
477 if (mVolumeMute
[button
] == 1) { // muted
478 image
= new Xpm(mDisplay
, mRoot
, mutebutton_xpm
);
479 image
->setWindowPixmap(mButton
[button
]);
482 XClearWindow(mDisplay
, mButton
[button
]);
484 image
= new Xpm(mDisplay
, mRoot
, button_xpm
);
485 image
->setWindowPixmap(mButton
[button
]);
488 XClearWindow(mDisplay
, mButton
[button
]);
492 void Mixer::setButtonPosition(int button
, int position
) {
493 if (position
> BUTTON_MIN
) {
494 position
= BUTTON_MIN
;
495 } else if (position
< BUTTON_MAX
) {
496 position
= BUTTON_MAX
;
499 XMoveWindow(mDisplay
, mButton
[button
], ButtonX
[button
], position
);
501 mVolumePos
[button
] = position
;
504 void Mixer::setButtonPositionRelative(int button
, int relativePosition
)
508 // Calc new button position
509 y
= mVolumePos
[button
] + relativePosition
;
511 if (y
> BUTTON_MIN
) {
513 } else if (y
< BUTTON_MAX
) {
517 // Set button position and volume
518 XMoveWindow(mDisplay
, mButton
[button
], ButtonX
[button
], y
);
520 mVolumePos
[button
] = y
;
523 setVolume(button
, positionToPercent(y
));
530 int buttonDownPosition
= 0;
532 // Start handling events
534 while(XPending(mDisplay
) || buttonDown
) {
535 XNextEvent(mDisplay
, &event
);
539 if (event
.xbutton
.button
== Button4
|| event
.xbutton
.button
== Button5
) {
541 setButtonPositionRelative(mWheelButton
- 1, event
.xbutton
.button
== Button5
? 3: -3);
542 } else if (event
.xbutton
.button
== Button1
&& event
.xbutton
.window
!= mIconWin
) {
545 buttonDownPosition
= event
.xbutton
.y
;
546 } else if (event
.xbutton
.button
== Button3
&& buttonDown
== 0 && event
.xbutton
.window
!= mIconWin
) {
548 for (int i
=0; i
<3; i
++) {
549 if (mButton
[i
] == event
.xbutton
.window
) {
554 } else if (event
.xbutton
.button
== Button2
) {
555 // Load defaults or execute command
559 snprintf(command
, 512, "%s &", mCommand
);
563 loadVolumeSettings();
568 if (event
.xbutton
.button
== Button1
) {
576 for (int i
=0; i
<3; i
++) {
577 if (mButton
[i
] == event
.xmotion
.window
) {
578 setButtonPositionRelative(i
, event
.xmotion
.y
- buttonDownPosition
);
590 // Update volume status
591 aMixer
->handleEvents();
592 if (AMixer::mixerChanged())
594 else if (AMixer::mixerElemsChanged())
596 XSync(mDisplay
, False
);