4 // Copyright (c) 1998-2002 Per Liden
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 2 of the License, or
9 // (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1307,
31 #include "AMixer/AMixer.h"
33 #include "pixmaps/main.xpm"
34 #include "pixmaps/button.xpm"
35 #include "pixmaps/mutebutton.xpm"
36 #include "pixmaps/redlight.xpm"
38 #define ROUND_POS(x) (int ((x) + 0.5) > int (x)) ? (int) ((x) + 1) : (int) (x)
42 static const int ButtonX
[] = {6, 24, 42};
43 static const char* MixerSources
[] = { "Master", "PCM", "CD" };
47 void catchBrokenPipe(int sig
)
49 app
->saveVolumeSettings();
53 int positionToPercent(int position
) {
54 return ROUND_POS(100 - (((position
- BUTTON_MAX
) * 100.0) / (BUTTON_MIN
- BUTTON_MAX
)));
57 int percentToPosition(int percent
) {
58 return ROUND_POS(BUTTON_MIN
- (percent
* (BUTTON_MIN
- BUTTON_MAX
)) / 100.0);
61 Mixer::Mixer(int argc
, char** argv
)
68 char* displayName
= NULL
;
69 char* card
= "default";
72 mInstanceName
= INSTANCENAME
;
73 mVolumeSource
[0] = -1;
74 mVolumeSource
[1] = -1;
75 mVolumeSource
[2] = -1;
82 mSaveSettings
= false;
83 mLoadSettings
= false;
88 for (int i
=1; i
<argc
; i
++) {
90 if (!strcmp(argv
[i
], "-d")) {
91 checkArgument(argv
, argc
, i
);
92 displayName
= argv
[i
+1];
97 else if (!strcmp(argv
[i
], "-1") || !strcmp(argv
[i
], "-2") || !strcmp(argv
[i
], "-3")) {
98 checkArgument(argv
, argc
, i
);
99 MixerSources
[argv
[i
][1] - '1'] = argv
[i
+ 1];
104 else if (!strcmp(argv
[i
], "-w")) {
105 checkArgument(argv
, argc
, i
);
106 mWheelButton
= atoi(argv
[i
+1]);
108 if (mWheelButton
< 1 || mWheelButton
> 3) {
109 cerr
<< APPNAME
<< ": invalid wheel binding, must be 1, 2 or 3, not " << argv
[i
+1] << endl
;
118 else if (!strcmp(argv
[i
], "-l")) {
119 checkArgument(argv
, argc
, i
);
120 mLabelText
= argv
[i
+1];
124 // Save settings on exit
125 else if (!strcmp(argv
[i
], "-S")) {
126 mSaveSettings
= true;
129 // Load settings on startup
130 else if (!strcmp(argv
[i
], "-L")) {
131 mLoadSettings
= true;
134 // Load/Save settings file
135 else if (!strcmp(argv
[i
], "-f")) {
136 checkArgument(argv
, argc
, i
);
137 mSettingsFile
= argv
[i
+1];
141 // Execute command on middle click
142 else if (!strcmp(argv
[i
], "-e")) {
143 checkArgument(argv
, argc
, i
);
144 mCommand
= argv
[i
+ 1];
149 else if (!strcmp(argv
[i
], "-n")) {
150 checkArgument(argv
, argc
, i
);
151 mInstanceName
= argv
[i
+1];
156 else if (!strcmp(argv
[i
], "-v")) {
157 cerr
<< APPNAME
<< " version " << VERSION
<< endl
;
162 else if (!strcmp(argv
[i
], "-h") || !strcmp(argv
[i
], "--help")) {
168 else if (!strcmp(argv
[i
], "--card")) {
169 card
= AMixer::convertIDToCard(argv
[i
+ 1]);
171 cerr
<< APPNAME
<< ": invalid card number '" << argv
[i
+ 1] << "'" << endl
;
179 else if (!strcmp(argv
[i
], "--device")) {
186 cerr
<< APPNAME
<< ": invalid option '" << argv
[i
] << "'" << endl
;
193 // default settings file
194 if (!mSettingsFile
) {
195 char* home
= getenv("HOME");
197 mSettingsFile
= new char[strlen(home
) + strlen(SETTINGS
) + 1];
198 strcpy(mSettingsFile
, home
);
199 strcat(mSettingsFile
, SETTINGS
);
201 cerr
<< APPNAME
<< ": $HOME not set, could not find saved settings" << endl
;
206 aMixer
= new AMixer(card
);
207 if (!aMixer
->opened()) {
208 cerr
<< APPNAME
<< ": could not open mixer device for card '" << card
<< "'" << endl
;
212 // open mixer sources
213 for (int i
= 0; i
< 3; i
++) {
214 aMixer
->attachItem(i
, MixerSources
[i
]);
215 if (!aMixer
->itemOK(i
))
216 cerr
<< APPNAME
<< ": could not select mixer source '" << MixerSources
[i
] << "'" << endl
;
220 if ((mDisplay
= XOpenDisplay(displayName
)) == NULL
) {
221 cerr
<< APPNAME
<< ": could not open display " << displayName
<< endl
;
226 mRoot
= RootWindow(mDisplay
, DefaultScreen(mDisplay
));
229 mAppWin
= XCreateSimpleWindow(mDisplay
, mRoot
, 1, 1, 64, 64, 0, 0, 0);
230 mIconWin
= XCreateSimpleWindow(mDisplay
, mAppWin
, 0, 0, 64, 64, 0, 0, 0);
233 classHint
.res_name
= mInstanceName
;
234 classHint
.res_class
= CLASSNAME
;
235 XSetClassHint(mDisplay
, mAppWin
, &classHint
);
237 // Create delete atom
238 deleteWindow
= XInternAtom(mDisplay
, "WM_DELETE_WINDOW", False
);
239 XSetWMProtocols(mDisplay
, mAppWin
, &deleteWindow
, 1);
240 XSetWMProtocols(mDisplay
, mIconWin
, &deleteWindow
, 1);
243 XStoreName(mDisplay
, mAppWin
, APPNAME
);
244 XSetIconName(mDisplay
, mAppWin
, APPNAME
);
247 sizeHints
.flags
= USPosition
;
250 XSetWMNormalHints(mDisplay
, mAppWin
, &sizeHints
);
253 wmHints
.initial_state
= WithdrawnState
;
254 wmHints
.icon_window
= mIconWin
;
257 wmHints
.window_group
= mAppWin
;
258 wmHints
.flags
= StateHint
| IconWindowHint
| IconPositionHint
| WindowGroupHint
;
259 XSetWMHints(mDisplay
, mAppWin
, &wmHints
);
262 XSetCommand(mDisplay
, mAppWin
, argv
, argc
);
264 // Set background image
265 image
= new Xpm(mDisplay
, mRoot
, main_xpm
);
267 image
->drawString(LABEL_X
, LABEL_Y
, mLabelText
);
269 image
->setWindowPixmapShaped(mIconWin
);
273 mButton
[0] = XCreateSimpleWindow(mDisplay
, mIconWin
, ButtonX
[0], BUTTON_MIN
, 5, 5, 0, 0, 0);
274 mButton
[1] = XCreateSimpleWindow(mDisplay
, mIconWin
, ButtonX
[1], BUTTON_MIN
, 5, 5, 0, 0, 0);
275 mButton
[2] = XCreateSimpleWindow(mDisplay
, mIconWin
, ButtonX
[2], BUTTON_MIN
, 5, 5, 0, 0, 0);
277 image
= new Xpm(mDisplay
, mRoot
, button_xpm
);
278 image
->setWindowPixmap(mButton
[0]);
279 image
->setWindowPixmap(mButton
[1]);
280 image
->setWindowPixmap(mButton
[2]);
283 XSelectInput(mDisplay
, mButton
[0], ButtonPressMask
| ButtonReleaseMask
| PointerMotionMask
);
284 XSelectInput(mDisplay
, mButton
[1], ButtonPressMask
| ButtonReleaseMask
| PointerMotionMask
);
285 XSelectInput(mDisplay
, mButton
[2], ButtonPressMask
| ButtonReleaseMask
| PointerMotionMask
);
286 XSelectInput(mDisplay
, mIconWin
, ButtonPressMask
);
288 XMapWindow(mDisplay
, mButton
[0]);
289 XMapWindow(mDisplay
, mButton
[1]);
290 XMapWindow(mDisplay
, mButton
[2]);
292 XMapWindow(mDisplay
, mIconWin
);
293 XMapWindow(mDisplay
, mAppWin
);
294 XSync(mDisplay
, False
);
296 // Catch broker pipe signal
297 signal(SIGPIPE
, catchBrokenPipe
);
305 loadVolumeSettings();
309 void Mixer::tryHelp(char* appname
)
311 cerr
<< "Try `" << appname
<< " --help' for more information" << endl
;
314 void Mixer::showHelp()
316 cerr
<< APPNAME
<< " Copyright (c) 1998-2002 by Per Liden (per@fukt.bth.se), Petr Hlavka (xhlavk00@stud.fit.vutbr.cz)" << endl
<< endl
317 << "options:" << endl
318 << " -1 <source> set sound source for control 1 (default is Master)" << endl
319 << " -2 <source> set sound source for control 2 (default is PCM)" << endl
320 << " -3 <source> set sound source for control 3 (default is CD)" << endl
321 << " -w 1|2|3 bind a control button to the mouse wheel (default is 1)" << endl
322 << " -l <text> set label text" << endl
323 << " -S save volume settings on exit" << endl
324 << " -L load volume settings on start up" << endl
325 << " -f <file> use setting <file> instead of ~/GNUstep/Defaults/AlsaMixer" << endl
326 << " --card <id> select card" << endl
327 << " --device <dev> select device, default 'default'" << endl
328 << " -e <command> execute <command> on middle click" << endl
329 << " -n <name> set client instance name" << endl
330 << " -d <disp> set display" << endl
331 << " -v print version and exit" << endl
332 << " -h, --help display this help and exit" << endl
<< endl
;
335 void Mixer::checkArgument(char** argv
, int argc
, int index
)
337 if (argc
-1 < index
+1) {
338 cerr
<< APPNAME
<< ": option '" << argv
[index
] << "' requires an argument" << endl
;
344 void Mixer::showErrorLed()
349 led
= XCreateSimpleWindow(mDisplay
, mIconWin
, LED_X
, LED_Y
, 3, 2, 0, 0, 0);
351 // Set background image
352 image
= new Xpm(mDisplay
, mRoot
, redlight_xpm
);
353 image
->setWindowPixmap(led
);
357 XMapWindow(mDisplay
, led
);
361 void Mixer::loadVolumeSettings()
364 ifstream
file(mSettingsFile
);
366 // This could fail if the user has edited the file by hand and destroyed the structure
369 file
>> dummy
; // Volume1
374 file
>> dummy
; // Volume2
379 file
>> dummy
; // Volume3
384 for (int i
= 0; i
< 3; i
++) {
385 setVolume(i
, mVolume
[i
]);
386 setButtonPosition(i
, percentToPosition(mVolume
[i
]));
392 void Mixer::saveVolumeSettings()
395 ofstream
file(mSettingsFile
);
397 // Files in ~/GNUstep/Defaults/ should follow the property list format
399 << " Volume1 = " << mVolumePos
[0] << ";" << endl
400 << " Volume2 = " << mVolumePos
[1] << ";" << endl
401 << " Volume3 = " << mVolumePos
[2] << ";" << endl
405 cerr
<< APPNAME
<< ": failed to save volume settings in " << mSettingsFile
<< endl
;
410 void Mixer::getVolume()
412 static int lastVolume
[3] = {-1, -1, -1};
413 static int lastVolumeMute
[3] = {-1, -1, -1};
420 for (int i
=0; i
<3; i
++) {
421 mVolume
[i
] = aMixer
->itemGetVolume(i
);
422 mVolumeMute
[i
] = aMixer
->itemIsMuted(i
);
424 if (lastVolume
[i
] != mVolume
[i
]) {
427 // Set button position
431 y
= percentToPosition(mVolume
[i
]);
434 setButtonPosition(i
, y
);
435 lastVolume
[i
] = mVolume
[i
];
438 // set buttom type muted/unmuted
439 if (lastVolumeMute
[i
] != mVolumeMute
[i
]) {
441 lastVolumeMute
[i
] = mVolumeMute
[i
];
446 cerr
<< APPNAME
<< ": unable to read from " << mMixerDevice
<< endl
;
452 void Mixer::setVolume(int button
, int volume
)
459 mVolume
[button
] = volume
;
462 aMixer
->itemSetVolume(button
, mVolume
[button
]);
465 void Mixer::toggleMute(int button
)
467 mVolumeMute
[button
] = !mVolumeMute
[button
];
468 aMixer
->itemToggleMute(button
);
469 setButtonType(button
);
472 void Mixer::setButtonType(int button
)
476 if (mVolumeMute
[button
] == 1) { // muted
477 image
= new Xpm(mDisplay
, mRoot
, mutebutton_xpm
);
478 image
->setWindowPixmap(mButton
[button
]);
481 XClearWindow(mDisplay
, mButton
[button
]);
483 image
= new Xpm(mDisplay
, mRoot
, button_xpm
);
484 image
->setWindowPixmap(mButton
[button
]);
487 XClearWindow(mDisplay
, mButton
[button
]);
491 void Mixer::setButtonPosition(int button
, int position
) {
492 if (position
> BUTTON_MIN
) {
493 position
= BUTTON_MIN
;
494 } else if (position
< BUTTON_MAX
) {
495 position
= BUTTON_MAX
;
498 XMoveWindow(mDisplay
, mButton
[button
], ButtonX
[button
], position
);
500 mVolumePos
[button
] = position
;
503 void Mixer::setButtonPositionRelative(int button
, int relativePosition
)
507 // Calc new button position
508 y
= mVolumePos
[button
] + relativePosition
;
510 if (y
> BUTTON_MIN
) {
512 } else if (y
< BUTTON_MAX
) {
516 // Set button position and volume
517 XMoveWindow(mDisplay
, mButton
[button
], ButtonX
[button
], y
);
519 mVolumePos
[button
] = y
;
522 setVolume(button
, positionToPercent(y
));
529 int buttonDownPosition
= 0;
531 // Start handling events
533 while(XPending(mDisplay
) || buttonDown
) {
534 XNextEvent(mDisplay
, &event
);
538 if (event
.xbutton
.button
== Button4
|| event
.xbutton
.button
== Button5
) {
540 setButtonPositionRelative(mWheelButton
- 1, event
.xbutton
.button
== Button5
? 3: -3);
541 } else if (event
.xbutton
.button
== Button1
&& event
.xbutton
.window
!= mIconWin
) {
544 buttonDownPosition
= event
.xbutton
.y
;
545 } else if (event
.xbutton
.button
== Button3
&& buttonDown
== 0 && event
.xbutton
.window
!= mIconWin
) {
547 for (int i
=0; i
<3; i
++) {
548 if (mButton
[i
] == event
.xbutton
.window
) {
553 } else if (event
.xbutton
.button
== Button2
) {
554 // Load defaults or execute command
558 snprintf(command
, 512, "%s &", mCommand
);
562 loadVolumeSettings();
567 if (event
.xbutton
.button
== Button1
) {
575 for (int i
=0; i
<3; i
++) {
576 if (mButton
[i
] == event
.xmotion
.window
) {
577 setButtonPositionRelative(i
, event
.xmotion
.y
- buttonDownPosition
);
589 // Update volume status
590 aMixer
->handleEvents();
591 if (AMixer::mixerChanged())
593 else if (AMixer::mixerElemsChanged())
595 XSync(mDisplay
, False
);