1 ////////////////////////////////////////
3 ////////////////////////////////////////
7 #define K8_MPLAYER_HACK
10 ////////////////////////////////////////////////////////////////////////////////
13 atomClientList
= None
,
17 ////////////////////////////////////////////////////////////////////////////////
18 static bool makeFacehugger (Window w
) {
19 //WARNING! this WILL NOT work as expected on our own windows!
20 return XSelectInput(QX11Info::display(), w
, /*SubstructureNotifyMask |*/ StructureNotifyMask
) == Success
;
24 static void dumpWinInfo (const char *msg
, Window w
) {
27 if (w
== None
) return;
28 pw
= X11Tools::windowTransientFor(w
);
29 qDebug() << msg
<< (int)(w
) <<
30 "class:" << X11Tools::windowClass(w
) <<
31 "name:" << X11Tools::windowName(w
) <<
32 "pid:" << X11Tools::windowPID(w
) <<
33 "transientfor:" << pw
;
34 //dumpWinInfo(" parent (tr)", pw);
38 ////////////////////////////////////////////////////////////////////////////////
39 Jrugr::Jrugr (int &argc
, char **argv
) :
40 QApplication(argc
, argv
),
47 mDeskJustChanged(false)
49 if (atomActWin
== None
) atomActWin
= XInternAtom(QX11Info::display(), "_NET_ACTIVE_WINDOW", True
);
50 if (atomClientList
== None
) atomClientList
= XInternAtom(QX11Info::display(), "_NET_CLIENT_LIST", True
);
51 if (atomCurDesk
== None
) atomCurDesk
= XInternAtom(QX11Info::display(), "_NET_CURRENT_DESKTOP", True
);
52 // mandatory to keep it running after closing about dialogs...
53 setQuitOnLastWindowClosed(false);
54 setWindowIcon(QIcon(":/about/jrugr.png"));
56 mCfgFileName
= QDir::homePath()+"/.config/jrugr.cfg";
64 //! \warning the trayIcon *has* to be deleted here to prevent XFreeColormap() free corruption.
69 bool Jrugr::firstStart () {
70 if (!QFile::exists(mCfgFileName
)) {
73 qDebug() << " Jrugr:Config file not found in:" << mCfgFileName
;
74 QSettings
jrugr(mCfgFileName
, QSettings::IniFormat
);
75 QDir
themeDir("/usr/share/jrugr/theme/default");
76 if (themeDir
.exists()) themePath
= themeDir
.path();
77 else themePath
= QCoreApplication::applicationDirPath()+"/theme/default";;
78 jrugr
.beginGroup("Style");
79 mLangThemePath
= themePath
;
80 jrugr
.setValue("path", mLangThemePath
);
81 jrugr
.endGroup(); //Style
88 void Jrugr::startup () {
89 if (firstStart()) configure();
92 QSettings
jrugr(mCfgFileName
, QSettings::IniFormat
, this);
93 jrugr
.beginGroup("Style");
94 mLangThemePath
= jrugr
.value("path").toString()+"/language/";
95 jrugr
.endGroup(); //Style
97 jCfg
= JrugrCfg::load(mCfgFileName
);
99 if (jCfg
->workMode
== USE_XKB
) configureXkb();
101 mXkb
= new XKeyboard();
102 mXkb
->groupInfo(mGroupInfo
);
104 connect(mXkb
, SIGNAL(groupChanged(int)), this, SLOT(onGroupChanged(int)), Qt::QueuedConnection
);
105 connect(mXkb
, SIGNAL(layoutChanged()), this, SLOT(onLayoutChanged()), Qt::QueuedConnection
);
106 connect(this, SIGNAL(changeLayout(int)), this, SLOT(onChangeLayout(int)), Qt::QueuedConnection
);
108 connect(this, SIGNAL(activeWindowChanged()), this, SLOT(doActiveWindowChanged()), Qt::QueuedConnection
);
109 connect(this, SIGNAL(activeDesktopChanged()), this, SLOT(doActiveDesktopChanged()), Qt::QueuedConnection
);
110 connect(this, SIGNAL(clientListChanged()), this, SLOT(onClientListChanged()), Qt::QueuedConnection
);
114 void Jrugr::initialize () {
115 mXkb
->groupInfo(mGroupInfo
);
117 mActiveGroup
= mXkb
->activeGroup();
118 mActiveWindow
= X11Tools::activeWindow();
119 mCurDesk
= X11Tools::activeDesktop();
121 mTrayIcon
= new QSystemTrayIcon(this);
122 connect(mTrayIcon
, SIGNAL(activated(QSystemTrayIcon::ActivationReason
)), this, SLOT(trayClicked(QSystemTrayIcon::ActivationReason
)));
127 installFacehuggers();
131 void Jrugr::reconfigure () {
132 JrugrCfg
*newConf
= JrugrCfg::load(mCfgFileName
);
137 bool diffGI
= nl
.size() != mGroupInfo
.size();
140 for (int f
= nl
.size()-1; f
>= 0; --f
) if (nl
[f
].name
!= mGroupInfo
[f
].name
|| nl
[f
].sym
!= mGroupInfo
[f
].sym
) { diffGI
= true; break; }
143 if (diffGI
|| newConf
->layouts
!= jCfg
->layouts
|| newConf
->showFlag
!= jCfg
->showFlag
||
144 newConf
->workMode
!= jCfg
->workMode
|| newConf
->switching
!= jCfg
->switching
) {
156 int Jrugr::setKeyLayout (QString keyConf
) {
157 qDebug() << "Jrugr::setKeyLayout:" << keyConf
;
158 QStringList argument
= keyConf
.split(" ");
159 qDebug() << " Jrugr:setxkbmap argumetns:" << argument
;
160 int result
= QProcess::execute("setxkbmap", argument
);
161 qDebug() << " Jrugr:setxkbmap result:" << result
;
166 void Jrugr::configureXkb () {
167 QString layout
, variant
, option
;
168 QString model
= jCfg
->model
;
170 for (int f
= 0; f
< jCfg
->layouts
.count(); ++f
) {
171 layout
+= jCfg
->layouts
[f
].layout
;
172 variant
+= jCfg
->layouts
[f
].variant
;
173 if (f
< jCfg
->layouts
.count()-1) { layout
+= ','; variant
+= ','; }
175 option
= jCfg
->options
.join(",");
176 qDebug() << "Set layout:" << layout
;
177 if (!(model
.isEmpty() || model
.isNull()) && !(layout
.isEmpty() || layout
.isNull())) {
178 QString args
= "-model "+model
+" -layout "+layout
;
179 QString tmp
= variant
;
180 if (!variant
.isNull() && !variant
.isEmpty() && !tmp
.remove(",").isEmpty()) args
+=" -variant "+variant
;
181 if (!option
.isNull() && !option
.isEmpty()) args
+=" -option "+option
;
182 qDebug() << " Jrugr:XKB args " << args
;
183 if (setKeyLayout(args
) == QProcess::CrashExit
) {
184 qDebug() << " Jrugr:XKB isn`t set";
185 qDebug() << " Jrugr:------------------------------";
188 qDebug() << " Jrugr:XKB set";
193 void Jrugr::configure () {
194 JrugrConfigForm
*mCfgForm
= new JrugrConfigForm(mCfgFileName
);
195 connect(mCfgForm
, SIGNAL(saveConfig()), SLOT(reconfigure()));
196 mCfgForm
->exec(); // the form will die on closing
200 bool Jrugr::isKnownWindow (Window w
) const {
201 return mKnownWindows
.contains(w
);
205 bool Jrugr::isKnownApp (Window w
) const {
206 return mKnownApps
.contains(X11Tools::appWindow(w
));
210 WinInfo
*Jrugr::findWindowInfo (Window w
) {
211 return mKnownWindows
.value(w
, 0);
215 // find application info for the given window
216 WinInfo
*Jrugr::findAppInfo (Window w
) {
217 return mKnownApps
.value(X11Tools::appWindow(w
), 0);
221 int Jrugr::desktopLayout (int desk
) const {
222 if (desk
< 0 || desk
>= mDeskLangs
.size()) return 0;
223 return mDeskLangs
.at(desk
);
227 void Jrugr::setDesktopLayout (int desk
, int group
) {
228 if (desk
< 0 || desk
> 42) return; // invalid desktop number
229 while (mDeskLangs
.size() <= desk
) mDeskLangs
<< 0;
230 mDeskLangs
[desk
] = group
;
234 void Jrugr::installFacehuggers () {
235 QX11WindowList wl
= X11Tools::topLevelWindows();
239 //QWidgetList twl = QApplication::allWidgets();
240 QWidgetList twl
= QApplication::topLevelWidgets();
241 foreach (QWidget
*wd
, twl
) {
242 WId w
= wd
->effectiveWinId();
243 if (w
!= None
) qtwl
<< w
;
247 for (int f
= 0; f
< wl
.size(); ++f
) {
248 if (wl
[f
] != None
&& !qtwl
.contains(wl
[f
]) && !isKnownWindow(wl
[f
])) {
249 WinInfo
*wi
= new WinInfo
;
252 wi
->appWindow
= X11Tools::appWindow(wl
[f
]);
254 mKnownWindows
[wl
[f
]] = wi
;
255 makeFacehugger(wi
->w
);
257 if (wi
->appWindow
== wi
->w
&& !isKnownApp(wi
->w
)) {
258 WinInfo
*w2
= new WinInfo
;
260 w2
->appWindow
= w2
->w
= wi
->w
;
261 w2
->layout
= wi
->layout
;
262 mKnownApps
[w2
->w
] = w2
;
269 void Jrugr::doActiveWindowChanged () {
270 //qDebug() << "doActiveWindowChanged";
271 onActiveWindowChanged(X11Tools::activeWindow());
275 void Jrugr::doActiveDesktopChanged () {
276 //qDebug() << "doActiveDesktopChanged";
277 onActiveDesktopChanged(X11Tools::activeDesktop());
281 void Jrugr::onLayoutChanged () {
282 qDebug()<<"Jrugr::onLayoutChanged : reconfig";
284 qDebug()<<"Jrugr::onLayoutChanged : done";
288 QIcon
Jrugr::langIcon (const QString
&lsym
, int wdt
, int hgt
, bool noFlag
) {
290 QString path
= mLangThemePath
+fixLayoutName(lsym
, jCfg
->useSU
);
291 QString SVGfile
= path
+".svg";
293 if (QFile::exists(SVGfile
)) {
294 QSvgRenderer
flagSVG(SVGfile
);
295 if (flagSVG
.isValid()) {
296 QPixmap
pix(wdt
, hgt
);
299 flagSVG
.render(&painter
, QRectF(0, 0, wdt
, hgt
));
305 QString PNGfile
= path
+".png";
306 if (QFile::exists(PNGfile
)) {
307 return QIcon(PNGfile
);
312 qDebug() << ":::" << lsym
;
313 QFont
font("Helvetica [Cronyx]", 15); //FIXME: should be configurable
314 //QFont font("Tahoma", 15);
316 font
.setLetterSpacing(QFont::PercentageSpacing
, 120);
317 QPixmap
pix(lsym
.length()*20, 26);
320 painter
.setFont(font
);
321 painter
.fillRect(pix
.rect(), Qt::darkGray
);
322 painter
.setPen(Qt::white
);
323 painter
.drawText(pix
.rect(), Qt::AlignVCenter
|Qt::AlignCenter
, lsym
);
330 void Jrugr::createMenu () {
332 mTrayMenu
= new QMenu();
333 connect(mTrayMenu
, SIGNAL(triggered(QAction
*)), SLOT(actionsActivate(QAction
*)));
338 for (int index
= 0; index
< mGroupInfo
.size(); ++index
) {
339 QString lname
= mGroupInfo
[index
].name
, lsym
= mGroupInfo
[index
].sym
;
341 QAction
*act
= new QAction(lname
, this);
342 act
->setIcon(langIcon(lsym
));
344 mTrayMenu
->addAction(act
);
347 mTrayMenu
->addSeparator();
348 QAction
*config
= new QAction(tr("Configure"), this);
349 config
->setData("configure");
350 mTrayMenu
->addAction(config
);
352 mTrayMenu
->addSeparator();
353 QAction
*about_jrugr
= new QAction(tr("About Jrugr"), this);
354 about_jrugr
->setData("about_jrugr");
355 mTrayMenu
->addAction(about_jrugr
);
357 QAction
*about_qt
= new QAction(tr("About Qt"), this);
358 about_qt
->setData("about_qt");
359 mTrayMenu
->addAction(about_qt
);
361 mTrayMenu
->addSeparator();
362 QAction
*jrugrExit
= new QAction(tr("Exit"), this);
363 jrugrExit
->setData("exit");
364 mTrayMenu
->addAction(jrugrExit
);
368 void Jrugr::buildIcon () {
369 if (!mTrayIcon
) return;
370 mTrayIcon
->setIcon(langIcon(mGroupInfo
[mActiveGroup
].sym
, 32, 22, !jCfg
->showFlag
));
371 mTrayIcon
->setToolTip(mGroupInfo
[mActiveGroup
].name
);
372 if (mTrayMenu
) mTrayIcon
->setContextMenu(mTrayMenu
);
377 void Jrugr::trayClicked (QSystemTrayIcon::ActivationReason reason
) {
379 case QSystemTrayIcon::Trigger
: setNextGroup(); break;
380 case QSystemTrayIcon::DoubleClick
: setNextGroup(); break;
381 case QSystemTrayIcon::MiddleClick
: setPrevGroup(); break;
387 // new layout activated; fix all necessary shit
388 void Jrugr::onGroupChanged (int index
) {
389 qDebug() << "Jrugr::onGroupChanged: index:" << index
;
390 mActiveGroup
= index
;
393 if (mActiveWindow
!= None
) {
394 WinInfo
*wi
= findWindowInfo(mActiveWindow
);
397 qDebug() << "new window";
398 installFacehuggers();
399 wi
= findWindowInfo(mActiveWindow
);
403 qDebug() << "fixing window layout";
405 if (jCfg
->switching
== APP_LAYOUT
) {
406 wi
= findAppInfo(mActiveWindow
);
408 qDebug() << "fixing app layout";
411 qWarning() << "***APP SHIT!***";
415 qWarning() << "***WINDOW SHIT!***";
419 setDesktopLayout(X11Tools::activeDesktop(), mActiveGroup
);
423 void Jrugr::setNextGroup () {
424 mXkb
->setActiveGroup(mActiveGroup
+1>=mGroupInfo
.size()?0:mActiveGroup
+1);
428 void Jrugr::setPrevGroup () {
429 mXkb
->setActiveGroup(mActiveGroup
-1<0?mGroupInfo
.size()-1:mActiveGroup
-1);
433 void Jrugr::windowDies (Window w
) {
435 qDebug("**************************** FACEHUGGER PARENT DIES ****************************");
439 QWIHashMutableIterator
i(mKnownWindows
);
440 while (i
.hasNext()) {
441 WinInfo
*wi
= i
.next().value();
445 if (wi
->w
== wi
->appWindow
) {
446 if (appW
== None
) appW
= wi
->w
;
447 else if (appW
!= wi
->w
) qWarning("*** WTF?! ***");
454 // remove application
455 QWIHashMutableIterator
i(mKnownApps
);
456 while (i
.hasNext()) {
457 WinInfo
*wi
= i
.next().value();
469 void Jrugr::onChangeLayout (int index
) {
470 qDebug() << "Jrugr::changeLayout:" << index
;
471 mXkb
->setActiveGroup(index
);
475 void Jrugr::actionsActivate (QAction
*action
) {
476 QString cmd
= action
->data().toString();
478 qDebug() << "Jrugr::actionsActivate() command" << cmd
;
479 if (cmd
== "configure") {
481 } else if (cmd
== "about_qt") {
483 } else if (cmd
== "about_jrugr") {
484 QMessageBox::about(0,
486 tr("<h2>Jrugr, the keyboard layout switcher</h2>"
487 "<b>Version</b>: %1<p>"
488 "Gui tool to configure XKB extentions of X server.<p>"
489 "(c) 2009-2011 Fedor Chelbarakh, Petr Vanek, Ketmar").arg(JRUGR_VERSION
));
490 } else if (cmd
== "exit") {
491 qDebug() << "Jrugr::actionsActivate() exit";
494 for (int f
= mGroupInfo
.size()-1; f
>= 0; --f
) {
495 if (cmd
== mGroupInfo
[f
].name
) {
496 emit
changeLayout(f
);
504 #ifdef K8_MPLAYER_HACK
505 static QX11WindowList
getPossibleActives (void) {
506 QX11WindowList res
, wl
= X11Tools::topLevelWindowStack();
507 int desk
= X11Tools::activeDesktop();
510 //qDebug() << "looking for window to refocus...";
511 for (int f
= 0; f
< wl
.size(); ++f
) {
513 int wd
= X11Tools::windowDesktop(w
);
516 // skip 'special' windows
517 QStringList sl
= X11Tools::windowStateNames(w
);
518 if (sl
.indexOf("_NET_WM_STATE_STICKY") < 0 &&
519 sl
.indexOf("_NET_WM_STATE_HIDDEN") < 0 &&
520 sl
.indexOf("_NET_WM_STATE_SHADED") < 0 &&
521 sl
.indexOf("_NET_WM_STATE_SKIP_TASKBAR") < 0 &&
522 sl
.indexOf("_NET_WM_STATE_ABOVE") < 0 &&
523 sl
.indexOf("_NET_WM_STATE_BELOW") < 0) {
524 if (X11Tools::windowClass(w
) == "xv") {
525 QString name
= X11Tools::windowName(w
).toLower();
526 if (name
== "mplayer" || name
== "mplayer2") continue;
528 if (wine
== None
&& X11Tools::windowClass(w
) == "explorer.exe") wine
= w
; else res
<< w
;
533 if (wine
!= None
) res
<< wine
;
539 static bool deactivateMPlayer (void) {
540 qDebug() << "onActiveWindowChanged";
541 Window aw
= X11Tools::activeWindow();
542 bool changeFocus
= false;
545 if (X11Tools::windowDesktop(aw
) == -1 && X11Tools::windowClass(aw
) == "xv" && X11Tools::windowName(aw
) == "MPlayer") {
546 qDebug() << "MPlayer hack!";
550 qDebug() << "Empty window hack!";
555 // find previous window
556 QX11WindowList wl
= getPossibleActives();
559 Window pw
= wl
.at(wl
.size()-1);
561 qDebug() << "CHANGING FOCUS!" <<
562 "class:" << X11Tools::windowClass(pw
) <<
563 "name:" << X11Tools::windowName(pw
) <<
564 "pid:" << X11Tools::windowPID(pw
);
566 X11Tools::setActiveWindow(pw
);
575 void Jrugr::onActiveWindowChanged (Window w
) {
576 qDebug() << "onActiveWindowChanged";
578 #ifdef K8_MPLAYER_HACK
579 if (mDeskJustChanged
) {
583 mDeskJustChanged
= false;
584 mActiveWindow
= None
;
585 qDebug() << "new active window: NONE";
588 qDebug() << "new active window:" << (int)w
<< "desktop:" << X11Tools::windowDesktop(w
);
589 dumpWinInfo("new active window:", w
);
591 if (mDeskJustChanged
) {
592 qDebug() << "*** mDeskJustChanged ***";
593 mDeskJustChanged
= false;
594 #ifdef K8_MPLAYER_HACK
595 if (deactivateMPlayer()) return;
600 WinInfo
*wi
= findWindowInfo(w
), *aw
;
602 qDebug() << "new window";
603 installFacehuggers();
604 wi
= findWindowInfo(w
);
607 switch (jCfg
->switching
) {
609 if ((aw
= findAppInfo(w
)) != findAppInfo(mActiveWindow
)) {
610 qDebug("application changed!");
612 if (aw
->layout
!= mActiveGroup
) {
613 qDebug("enforcing layout %d", aw
->layout
);
614 emit
changeLayout(aw
->layout
);
617 qWarning("*** APP SHIT! ***");
622 if (wi
->layout
!= mActiveGroup
) {
623 qDebug("enforcing layout %d", wi
->layout
);
624 emit
changeLayout(wi
->layout
);
635 void Jrugr::onClientListChanged () {
636 qDebug() << "onClientListChanged";
637 installFacehuggers();
641 void Jrugr::onActiveDesktopChanged (int desk
) {
642 qDebug() << "onActiveDesktopChanged:" << desk
;
643 mDeskJustChanged
= true;
644 #ifdef K8_MPLAYER_HACK
645 if (deactivateMPlayer()) mDeskJustChanged
= false;
647 if (jCfg
->switching
== DESK_LAYOUT
&& mCurDesk
!= desk
) {
648 int ll
= desktopLayout(desk
);
649 setDesktopLayout(desk
, ll
); // expand desktop list
650 if (ll
!= mActiveGroup
) {
651 qDebug("enforce layout %d for desktop %d", ll
, desk
);
652 emit
changeLayout(mDeskLangs
.at(desk
));
659 bool Jrugr::x11EventFilter (XEvent
*event
) {
661 //qDebug() << "Jrugr::x11EventFilter:" << x11EventName(event);
662 switch (event
->type
) {
664 windowDies(event
->xdestroywindow
.window
);
667 if (event
->xproperty
.state
== PropertyNewValue
) {
668 if (atomActWin
!= None
&& event
->xproperty
.atom
== atomActWin
) {
669 //qDebug() << "emit: activeWindowChanged";
670 emit
activeWindowChanged();
672 if (atomCurDesk
!= None
&& event
->xproperty
.atom
== atomCurDesk
) {
673 //qDebug() << "emit: activeDesktopChanged";
674 emit
activeDesktopChanged();
676 if (atomClientList
!= None
&& event
->xproperty
.atom
== atomClientList
) {
677 qDebug() << "emit: clientListChanged";
678 emit
clientListChanged();
681 //qDebug() << "PropertyNotify complete";
684 mXkb
->processEvent(event
);
688 return false; // normal dispatching