Commit 455dbea1 authored by Cedric Bellegarde's avatar Cedric Bellegarde
Browse files

GUI: Add support for appmenu: kded-appmenu.

This module export menubar through dbus:
- it can display a global menubar at top screen
- it can display a popup menu at a wanted position (see next kwin commit)
- it can be used by applications like krunner-appmenu or plasma-widget-menubar
parent 0ad025d1
......@@ -165,6 +165,7 @@ if(${KDE_PLATFORM_PROFILE} STREQUAL "Desktop")
macro_optional_add_subdirectory(khotkeys)
macro_optional_add_subdirectory(kwrited)
macro_optional_add_subdirectory(ksystraycmd)
macro_optional_add_subdirectory(appmenu)
# data
macro_optional_add_subdirectory(cursors)
endif(NOT WIN32)
......
include_directories(
${CMAKE_SOURCE_DIR}
${CMAKE_BINARY_DIR}
)
include_directories(${DBUSMENUQT_INCLUDE_DIR})
set(kded_appmenu_SRCS
appmenu.cpp
menuimporter.cpp
appmenu_dbus.cpp
menubutton.cpp
menuwidget.cpp
menubar.cpp
topmenubar.cpp
glowbar.cpp
verticalmenu.cpp
)
qt4_add_dbus_adaptor(kded_appmenu_SRCS com.canonical.AppMenu.Registrar.xml
menuimporter.h MenuImporter menuimporteradaptor MenuImporterAdaptor)
qt4_add_dbus_adaptor(kded_appmenu_SRCS org.kde.kded.appmenu.xml
appmenu_dbus.h AppmenuDBus appmenuadaptor AppmenuAdaptor)
kde4_add_plugin(kded_appmenu ${kded_appmenu_SRCS})
target_link_libraries(kded_appmenu ${KDE4_KIO_LIBS} ${X11_LIBRARIES} ${DBUSMENUQT_LIBRARIES} ${KDE4_PLASMA_LIBS})
install(TARGETS kded_appmenu DESTINATION ${PLUGIN_INSTALL_DIR} )
########### install files ###############
install( FILES appmenu.desktop DESTINATION ${SERVICES_INSTALL_DIR}/kded )
install( FILES com.canonical.AppMenu.Registrar.xml DESTINATION ${DBUS_INTERFACES_INSTALL_DIR} )
install( FILES org.kde.kded.appmenu.xml DESTINATION ${DBUS_INTERFACES_INSTALL_DIR} )
/*
This file is part of the KDE project.
Copyright (c) 2011 Lionel Chauvin <megabigbug@yahoo.fr>
Copyright (c) 2011,2012 Cédric Bellegarde <gnumdk@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include "appmenu.h"
#include "kdbusimporter.h"
#include "menuimporteradaptor.h"
#include "appmenuadaptor.h"
#include "appmenu_dbus.h"
#include "topmenubar.h"
#include "verticalmenu.h"
#include <QDBusInterface>
#include <QDBusReply>
#include <QDBusPendingCallWatcher>
#include <QMenu>
#include <QApplication>
#include <QDesktopWidget>
#include <KDebug>
#include <KWindowSystem>
#include <KWindowInfo>
#include <KConfig>
#include <KConfigGroup>
#include <kpluginfactory.h>
#include <kpluginloader.h>
#include <netwm.h>
K_PLUGIN_FACTORY(AppMenuFactory,
registerPlugin<AppMenuModule>();
)
K_EXPORT_PLUGIN(AppMenuFactory("appmenu"))
AppMenuModule::AppMenuModule(QObject* parent, const QList<QVariant>&)
: KDEDModule(parent),
m_parent(parent),
m_menuImporter(0),
m_appmenuDBus(new AppmenuDBus(parent)),
m_menubar(0),
m_menu(0),
m_menuTimer(new QTimer(this)),
m_screenTimer(new QTimer(this)),
m_waitingAction(0),
m_currentScreen(-1)
{
reconfigure();
m_appmenuDBus->connectToBus();
m_currentScreen = currentScreen();
connect(m_appmenuDBus, SIGNAL(appShowMenu(int, int, WId)), SLOT(slotShowMenu(int, int, WId)));
connect(m_appmenuDBus, SIGNAL(moduleReconfigure()), SLOT(reconfigure()));
// transfer our signals to dbus
connect(this, SIGNAL(showRequest(qulonglong)), m_appmenuDBus, SIGNAL(showRequest(qulonglong)));
connect(this, SIGNAL(menuAvailable(qulonglong)), m_appmenuDBus, SIGNAL(menuAvailable(qulonglong)));
connect(this, SIGNAL(clearMenus()), m_appmenuDBus, SIGNAL(clearMenus()));
connect(this, SIGNAL(menuHidden(qulonglong)), m_appmenuDBus, SIGNAL(menuHidden(qulonglong)));
connect(this, SIGNAL(WindowRegistered(qulonglong, const QString&, const QDBusObjectPath&)),
m_appmenuDBus, SIGNAL(WindowRegistered(qulonglong, const QString&, const QDBusObjectPath&)));
connect(this, SIGNAL(WindowUnregistered(qulonglong)), m_appmenuDBus, SIGNAL(WindowUnregistered(qulonglong)));
connect(m_menuTimer, SIGNAL(timeout()), this, SLOT(slotShowCurrentWindowMenu()));
}
AppMenuModule::~AppMenuModule()
{
emit clearMenus();
hideMenubar(m_menubar);
delete m_menuImporter;
delete m_appmenuDBus;
}
void AppMenuModule::slotShowMenu(int x, int y, WId id)
{
// If menu visible, hide it
if (m_menu && m_menu->isVisible()) {
m_menu->hide();
return;
}
//dbus call by user (for khotkey shortcut)
if (x == -1 || y == -1) {
// We do not know kwin button position, so tell kwin to show menu
emit showRequest(KWindowSystem::self()->activeWindow());
return;
}
KDBusMenuImporter *importer = getImporter(id);
QMenu *menu = importer ? importer->menu() : 0;
// Window do not have menu
if (!menu) {
return;
}
m_menu = new VerticalMenu();
m_menu->setMenuBarParentWid(id);
// Populate menu
foreach (QAction *action, menu->actions()) {
m_menu->addAction(action);
}
m_menu->popup(QPoint(x, y));
// Activate waiting action if exist
if (m_waitingAction) {
m_menu->setActiveAction(m_waitingAction);
m_waitingAction = 0;
}
connect(m_menu, SIGNAL(aboutToHide()), this, SLOT(slotAboutToHide()));
// Application may crash and original menu destroyed
connect(menu, SIGNAL(destroyed()), this, SLOT(slotAboutToHide()), Qt::UniqueConnection);
}
void AppMenuModule::slotAboutToHide()
{
if (m_menu) {
emit menuHidden(m_menu->menuBarParentWid());
m_menu->deleteLater();
m_menu = 0;
}
}
// New window registered
// Do not get importer here as it can freeze module on session loading
void AppMenuModule::slotWindowRegistered(WId id, const QString& service, const QDBusObjectPath& path)
{
KDBusMenuImporter* importer = m_importers.take(id);
if (importer) {
delete importer;
}
if (m_menuStyle == "ButtonVertical") {
// Tell Kwin menu is available
emit menuAvailable(id);
} else if ( m_menuStyle == "TopMenuBar" && id == KWindowSystem::self()->activeWindow()) {
// Application already active so check if we need create menubar
slotActiveWindowChanged(id);
}
// Send a signal on bus for others dbus interface registrars
emit WindowRegistered(id, service, path);
}
// Window unregistered
void AppMenuModule::slotWindowUnregistered(WId id)
{
KDBusMenuImporter* importer = m_importers.take(id);
if (importer) {
importer->deleteLater();
}
if (m_menubar && m_menubar->menuBarParentWid() == id) {
hideMenubar(m_menubar);
}
// Send a signal on bus for others dbus interface registrars
emit WindowUnregistered(id);
}
// Keyboard activation requested, transmit it to menu
void AppMenuModule::slotActionActivationRequested(QAction* a)
{
// If we have a topmenubar, activate action
if (m_menubar) {
m_menubar->setActiveAction(a);
m_menubar->show();
} else { // else send request to kwin or others dbus interface registrars
m_waitingAction = a;
emit showRequest(KWindowSystem::self()->activeWindow());
}
}
// Current window change, update menubar
// See comments in slotWindowRegistered() for why we get importers here
void AppMenuModule::slotActiveWindowChanged(WId id)
{
bool firstCall = !m_menuTimer->isActive();
KWindowInfo info = KWindowSystem::windowInfo(id, NET::WMWindowType);
unsigned long mask = NET::AllTypesMask;
// Active window changed, miss previous menu
if (m_menuTimer->isActive()) {
m_menuTimer->stop();
}
if (id == 0) {// Ignore root window
return;
} else if (info.windowType(mask) & NET::Dock) { // hide immediatly menubar for docks (krunner)
hideMenubar(m_menubar);
return;
}
if (!m_menuImporter->serviceExist(id)) { // No menu exist, check for another menu for application
if (! (id = m_menuImporter->recursiveMenuId(id))) {
if (firstCall) {
// Some windows (gtk) get menu after being actives,
// try another time before hiding previous menubar
m_menuTimer->start(1000);
} else {
// Window do not seems to have a menu
hideMenubar(m_menubar);
}
return;
}
}
KDBusMenuImporter *importer = getImporter(id);
QMenu *menu = importer->menu();
// length == 0 means menu not ready
// Start a m_menuTimer looking for menu to be ready
if(menu->actions().length()) {
showTopMenuBar(menu);
m_menubar->setMenuBarParentWid(id);
} else {
m_menuTimer->start(500);
}
}
void AppMenuModule::slotShowCurrentWindowMenu()
{
slotActiveWindowChanged(KWindowSystem::self()->activeWindow());
}
void AppMenuModule::slotCurrentScreenChanged()
{
if (m_currentScreen != currentScreen()) {
if (m_menubar) {
m_menubar->setMenuBarParentWid(0);
}
slotActiveWindowChanged(KWindowSystem::self()->activeWindow());
}
}
// reload settings
void AppMenuModule::reconfigure()
{
KConfig config( "kdeglobals", KConfig::FullConfig );
KConfigGroup configGroup = config.group("Appmenu Style");
m_menuStyle = configGroup.readEntry("Style", "InApplication");
// Clean up current menu
m_waitingAction = 0;
hideMenubar(m_menubar); // hide menubar if exist
slotAboutToHide(); // hide vertical menu if exist
// Disconnect all options specifics signals
disconnect(KWindowSystem::self(), SIGNAL(activeWindowChanged(WId)), this, SLOT(slotActiveWindowChanged(WId)));
disconnect(KWindowSystem::self(), SIGNAL(workAreaChanged()), this, SLOT(slotShowCurrentWindowMenu()));
disconnect(m_screenTimer, SIGNAL(timeout()), this, SLOT(slotCurrentScreenChanged()));
m_screenTimer->stop();
// Tell kwin to clean its titlebar
emit clearMenus();
if (m_menuStyle == "InApplication") {
if (m_menuImporter) {
delete m_menuImporter;
m_menuImporter = 0;
}
return;
}
// Setup a menu importer if needed
if (!m_menuImporter) {
m_menuImporter = new MenuImporter(m_parent);
connect(m_menuImporter, SIGNAL(WindowRegistered(WId, const QString&, const QDBusObjectPath&)),
SLOT(slotWindowRegistered(WId, const QString&, const QDBusObjectPath&)));
connect(m_menuImporter, SIGNAL(WindowUnregistered(WId)),
SLOT(slotWindowUnregistered(WId)));
m_menuImporter->connectToBus();
}
if( m_menuStyle == "ButtonVertical" ) {
foreach(WId id, m_menuImporter->ids()) {
emit menuAvailable(id);
}
}
// Setup top menubar if needed
if (m_menuStyle == "TopMenuBar") {
connect(KWindowSystem::self(), SIGNAL(activeWindowChanged(WId)), this, SLOT(slotActiveWindowChanged(WId)));
connect(KWindowSystem::self(), SIGNAL(workAreaChanged()), this, SLOT(slotShowCurrentWindowMenu()));
connect(m_screenTimer, SIGNAL(timeout()), this, SLOT(slotCurrentScreenChanged()));
m_screenTimer->start(1000);
slotShowCurrentWindowMenu();
}
}
KDBusMenuImporter* AppMenuModule::getImporter(WId id)
{
KDBusMenuImporter* importer = 0;
if (m_importers.contains(id)) { // importer already exist
importer = m_importers.value(id);
} else { // get importer
importer = new KDBusMenuImporter(id, m_menuImporter->serviceForWindow(id), &m_icons,
m_menuImporter->pathForWindow(id), this);
m_importers.insert(id, importer);
connect(importer, SIGNAL(actionActivationRequested(QAction*)),
SLOT(slotActionActivationRequested(QAction*)));
QMetaObject::invokeMethod(importer, "updateMenu", Qt::DirectConnection);
}
return importer;
}
void AppMenuModule::showTopMenuBar(QMenu *menu)
{
TopMenuBar *previous = m_menubar;
m_menubar = new TopMenuBar(menu);
QDesktopWidget *desktop = QApplication::desktop();
m_currentScreen = currentScreen();
QRect screen = desktop->availableGeometry(m_currentScreen);
int x = screen.center().x() - m_menubar->sizeHint().width()/2;
m_menubar->move(QPoint(x, screen.topLeft().y()));
m_menubar->enableMouseTracking();
hideMenubar(previous);
}
void AppMenuModule::hideMenubar(TopMenuBar *menubar)
{
if (menubar) {
if (menubar->isVisible()) {
menubar->hide();
}
delete menubar;
if (m_menubar == menubar) {
m_menubar = 0;
}
}
}
int AppMenuModule::currentScreen()
{
KWindowInfo info = KWindowSystem::windowInfo(KWindowSystem::self()->activeWindow(),
NET::WMGeometry);
int x = info.geometry().x();
int y = info.geometry().y();
QDesktopWidget *desktop = QApplication::desktop();
return desktop->screenNumber(QPoint(x,y));
}
#include "appmenu.moc"
[Desktop Entry]
Type=Service
Name=Application menus daemon
Name[da]=Dæmon til programmenuer
Name[el]=Δαίμονας μενού εφαρμογών
Name[es]=Demonio de menús de aplicaciones
Name[et]=Rakenduste menüü deemon
Name[fi]=Sovellusvalikon palvelu
Name[fr]=Démon des menus d'applications
Name[hu]=Alkalmazásmenü démon
Name[km]=ដេមិន​ម៉ឺនុយ​កម្មវិធី
Name[nl]=Daemon voor menu's van toepassingen
Name[pl]=Demon menu programów
Name[pt]=Servidor dos menus da aplicação
Name[pt_BR]=Servidor dos menus do aplicativo
Name[sk]=Démon ponúk aplikácie
Name[sv]=Demon för programmenyer
Name[uk]=Фонова служба меню програм
Name[x-test]=xxApplication menus daemonxx
Name[zh_TW]=應用程式選單伺服程式
Comment=Transfers application's menu to the desktop
Comment[cs]=Přesouvá nabídku aplikací na plochu
Comment[da]=Overfører programmenuer til skrivebordet
Comment[de]=Überträgt Anwendungsmenüs auf die Arbeitsfläche
Comment[el]=Μεταφέρει το μενού της εφαρμογής στην επιφάνεια εργασίας
Comment[es]=Transfiere menús de aplicaciones al escritorio
Comment[et]=Rakenduste menüü paigutamine töölauale
Comment[fi]=Siirtää sovelluksen valikon työpöydälle
Comment[fr]=Transfère le menu de l'application sur le bureau
Comment[hu]=Átviszi az alkalmazások menüjét az asztalra
Comment[km]=ផ្ទេរ​ម៉ឺនុយ​របស់​កម្មវិធី​ទៅ​ផ្ទៃតុ
Comment[nl]=Verplaatst menu's van toepassingen naar het bureaublad
Comment[pl]=Przenosi menu aplikacji na pulpit
Comment[pt]=Transfere o menu da aplicação para o ecrã
Comment[pt_BR]=Transfere o menu do aplicativo para a área de trabalho
Comment[sk]=Presunúť ponuky aplikácie na plochu
Comment[sv]=Överför programmets meny till skrivbordet
Comment[uk]=Передає меню програм на стільницю
Comment[x-test]=xxTransfers application's menu to the desktopxx
Comment[zh_TW]=將應用程式選單傳送到桌面
X-KDE-ServiceTypes=KDEDModule
X-KDE-Library=appmenu
X-KDE-DBus-ModuleName=appmenu
X-KDE-Kded-autoload=true
X-KDE-Kded-load-on-demand=false
/*
This file is part of the KDE project.
Copyright (c) 2011 Lionel Chauvin <megabigbug@yahoo.fr>
Copyright (c) 2011,2012 Cédric Bellegarde <gnumdk@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#ifndef APPMENUMODULE_H
#define APPMENUMODULE_H
#include <kdedmodule.h>
#include "menuimporter.h"
#include "gtkicons.h"
class QDBusPendingCallWatcher;
class KDBusMenuImporter;
class AppmenuDBus;
class TopMenuBar;
class VerticalMenu;
class AppMenuModule : public KDEDModule,
protected QDBusContext
{
Q_OBJECT
public:
AppMenuModule(QObject* parent, const QList<QVariant>& list);
virtual ~AppMenuModule();
Q_SIGNALS:
/**
* We do not know where is menu decoration button, so tell kwin to show menu
*/
void showRequest(qulonglong);
/**
* This signal is emitted whenever application menu becomes available
*/
void menuAvailable(qulonglong);
/**
* This signal is emitted whenever menus are unavailables
*/
void clearMenus();
/**
* This signal is emitted whenever popup menu/menubar is hidden
* Useful for decorations to know if menu button should be release
*/
void menuHidden(qulonglong);
/**
* This signal is emitted whenever a window register to appmenu
*/
void WindowRegistered(qulonglong wid, const QString& service, const QDBusObjectPath&);
/**
* This signal is emitted whenever a window unregister from appmenu
*/
void WindowUnregistered(qulonglong wid);
private Q_SLOTS:
/**
* Show menu at QPoint(x,y) for id
* if x or y == -1, show in application window
*/
void slotShowMenu(int x, int y, WId);
/**
* Send menuHidden signal over bus when menu is about to hide
*/
void slotAboutToHide();
/**
* New window registered to appmenu
* Emit WindowRegistered signal over bus
*/
void slotWindowRegistered(WId id, const QString& service, const QDBusObjectPath& path);
/**
* Window unregistered from appmenu
* Emit WindowUnregistered signal over bus
*/
void slotWindowUnregistered(WId id);
/**
* Open a action in current menu
*/
void slotActionActivationRequested(QAction* a);
/**
* Active window changed, update menubar
*/
void slotActiveWindowChanged(WId id);
/**
* Update menubar with current window menu
*/
void slotShowCurrentWindowMenu();
/**
* Current screen changed, update menubar
*/
void slotCurrentScreenChanged();
/**
* Reconfigure module
*/
void reconfigure();
private:
/**
* return an importer for window id
*/
KDBusMenuImporter* getImporter(WId id);
/**
* Show top menubar with menu
*/
void showTopMenuBar(QMenu *menu);
/**
* Hide menubar. Delete object
*/
void hideMenubar(TopMenuBar* menubar);
/**
* Return current screen
*/
int currentScreen();
QObject* m_parent;
MenuImporter* m_menuImporter;
AppmenuDBus* m_appmenuDBus;
QHash<WId, KDBusMenuImporter*> m_importers;
GtkIcons m_icons;
QString m_menuStyle;
TopMenuBar* m_menubar;
VerticalMenu* m_menu;
QTimer* m_menuTimer;
QTimer* m_screenTimer;
QAction *m_waitingAction;
int m_currentScreen;
};
#endif
/*
This file is part of the KDE project.
Copyright (c) 2011 Lionel Chauvin <megabigbug@yahoo.fr>
Copyright (c) 2011,2012 Cédric Bellegarde <gnumdk@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions: