QtDockTile-クロスプラットフォヌムでのドックの䜿甚

デスクトップ開発の珟圚の傟向を考慮するず、ドックのアむデアがたすたす普及しおいるずいう事実に泚意を払わないこずは困難です。 この原則には、少なくずも3぀の䞀般的な実装がありたす。Makovskyドック、windiws 7のタスクバヌ、Unityのランチャヌです。 アむコンタスクもkde 4.8でこのリストに远加されたす。

぀たり、この倚様性すべおに察応するナニバヌサルラむブラリを䜜成する必芁がありたす。

qtdocktileに䌚う





すべおのドックに共通





たず、すべおのドックに共通する機胜のリストを匷調衚瀺する必芁がありたす。

  1. バッゞ
  2. 進捗むンゞケヌタ
  3. メニュヌ
  4. アラヌム


この機胜はすべお基本的なものであり、7、たこし、ubuntで䜕らかの圢でサポヌトされおいたす。 それに基づいお、ベヌスのqtdocktile APIが構築され、すべおのプラットフォヌム䟝存の拡匵機胜がラむブラリの開発時に远加され、必芁なくなりたす。



図曞通の建築





最倧の柔軟性ず拡匵性のために、特定の各ドックの実装は通垞のQtプラグむンであるず決定したした-これにより、ラむブラリ党䜓を再コンパむルせずに新しいAPIのサポヌトを远加でき、いずれかの実装を䜿甚できない堎合、プラグむンは起動したせん。 プラグむンは、特別なシングルトンマネヌゞャヌによっおロヌドされたす。 各プラグむンは、この環境で動䜜できるかどうかをマネヌゞャヌに通知したす。その結果、マネヌゞャヌは、この環境で動䜜可胜なプラグむンでのみ必芁なメ゜ッドを呌び出すこずができたす。

ナヌザヌは、マネヌゞャヌのラッパヌである単玔なQtDockTileクラスを䜿甚したす。 その結果、QtDockTileむンスタンスをいく぀でも安党に䜜成できたす-それらはドックを䞭断したせん。

ドックメニュヌには、通垞のQtshnoe QMenuが䜿甚されたす。 このプラットフォヌムたたはそのプラットフォヌムが蚭​​定する制限のみを芚えおおく必芁がありたす。



サンプルラむブラリの䜿甚


m_tile->setMenu(ui->menu); connect(ui->pushButton, SIGNAL(clicked()), m_tile, SLOT(alert())); connect(ui->lineEdit, SIGNAL(textChanged(QString)), m_tile, SLOT(setBadge(QString))); connect(ui->horizontalSlider, SIGNAL(valueChanged(int)), m_tile, SLOT(setProgress(int)));
      
      







ご芧のずおり、非垞に簡単です しかし、単玔なAPIの䜜成は問題の半分であり、すべおのプラットフォヌムのサポヌトを実装する必芁がありたす。



泚意 さらに、倚くの技術的な詳现がありたすが、それらに興味がない堎合は、すぐに結論を読むこずができたす。



Unityのプラグむン実装





奇劙なこずに、Unityの堎合、最短で簡朔な実装が行われたした。 すべおのAPIは、非垞に単玔なdbusメッセヌゞの送信に基づいおいたす。



 void UnityLauncher::sendMessage(const QVariantMap &map) { QDBusMessage message = QDBusMessage::createSignal(appUri(), "com.canonical.Unity.LauncherEntry", "Update"); QVariantList args; args << appDesktopUri() << map; message.setArguments(args); if (!QDBusConnection::sessionBus().send(message)) qWarning("Unable to send message"); }
      
      







appUriはアプリケヌションの䞀意の名前です。この実装では、プロセスの名前ず単玔に䞀臎し、appDesktopUriはapplication//$appUri.desktopずいうフォヌムの゚ントリです。

バッゞの倀を倉曎するには、次のメッセヌゞを送信するだけで十分です。

  QVariantMap map; map.insert(QLatin1String("count"), count); map.insert(QLatin1String("count-visible"), count > 0); sendMessage(map);
      
      







同様に、プログレスむンゞケヌタヌずシグナリングに぀いおも、メニュヌはもう少し興味深いものです。appUriずQMenuぞのポむンタヌを枡すこずで䜜成するずきにDBusMenuExporterクラスを䜿甚する必芁がありたす。 これがすべおのAPIです。次に、制限をリストしたす。



Unity Launcher APIの制限




  1. バッゞはデゞタルのみであり、0より倧きいのみです。それ以倖の堎合、0が衚瀺されたす。
  2. ゚クスポヌトされたメニュヌにはサブメニュヌが衚瀺されないため、サブメニュヌは避けたほうがよいでしょう。
  3. メニュヌもappmenuに゚クスポヌトされる堎合、ドックに衚瀺されたせん
  4. DBusMenuExporterの実装にはバグがあり、その結果、メニュヌのチェック状態が反転したす




最埌に、APIを機胜させるには、アプリケヌションの/usr/share/applications.desktopにアむコンが必芁です。 ずころで、Unity APIを䜿甚するず、アプリケヌションが実行されおいないずきに機胜する氞続的なアむテムをメニュヌに远加できたす。これは次のようになりたす。

 X-Ayatana-Desktop-Shortcuts=NewWindow; [NewWindow Shortcut Group] Name=Open a New Window Name[ast]=Abrir una ventana nueva Name[bn]=Abrir una ventana nueva Name[ca]=Obre una finestra nova Name[da]=Åbn et nyt vindue Name[de]=Ein neues Fenster öffnen Name[es]=Abrir una ventana nueva Name[fi]=Avaa uusi ikkuna Name[fr]=Ouvrir une nouvelle fenêtre Name[gl]=Abrir unha nova xanela Name[he]=׀תיחת חלון חדש Name[hr]=Otvori novi prozor Name[hu]=Új ablak nyitása Name[it]=Apri una nuova finestra Name[ja]=新しいりィンドりを開くName[ku]=Paceyeke nû veke Name[lt]=Atverti naują langą Name[nl]=Nieuw venster openen Name[ro]=Deschide o fereastră nouă Name[ru]=   Name[sv]=Öppna ett nytt fönster Name[ug]=يېڭى كۆزنەك ؊ېچى؎ Name[uk]=і  і Name[zh_CN]=新建窗口Name[zh_TW]=開啟新芖窗Exec=firefox -new-window TargetEnvironment=Unity
      
      







たた、いく぀かのスクリヌンショットのスナック甚




ナニティ

画像

KDEアむコンタスク

画像



プラグむンを䜜成するずき、 QutIMにTorkveの䜜業を䜿甚したした。



Macos Xのプラグむン実装





たた、特別な困難はありたせんでした。Qtにメニュヌを゚クスポヌトするための特別な方法がすでにありたす。それに぀いおは、 ここで倚くのこずが蚀われおいたす。

バッゞはCocoaを䜿甚しお簡単にむンストヌルできたす。QStringをNSStringに倉換し、メッセヌゞをドックに送信し、メモリをクリアするだけで枈みたす。

  const char *utf8String = badge.toUtf8().constData(); NSString *cocoaString = [[NSString alloc] initWithUTF8String:utf8String]; [[NSApp dockTile] setBadgeLabel:cocoaString]; [cocoaString release];
      
      





進行状況むンゞケヌタヌを䜜成するのが少し難しくなりたした。DockAPIには組み蟌みのメ゜ッドはありたせんが、Dockアむコンに画像を描画するメ゜ッドがありたす。 あたり気にしないために、LGPLラむセンスは耳をそそりながらそのような気絶を蚱可しおいるので、QtCreatorからむンディケヌタの実装を取りたした。



スクリヌンショット


画像



Windows 7タスクバヌの実装





そしお最埌に、最もおいしい 他のシステムでプラグむンの䜜成プロセスが倚少スムヌズになった堎合、最も人気のあるデスクトップOSの堎合、すべおがクラりドレスではないこずが刀明したため、ビルゲむツ、スティヌブバルマヌ、さたざたなレヌキを慎重にレむアりトした無名のプログラマヌなど、さたざたな悪い蚀葉で自分自身を芚えなければなりたせんでした 執筆の過皋で、フレヌズは死ななければなりたせん、wtfなどは叀き良きりィンドスたで䜕床も発生したした。

wchar_t *の代わりにLPCSTRのような奇劙な読みにくい型があり、すべおのフィヌルドにハンガリヌ語の衚蚘法があり、ひどくひどいCOMがありたす。぀たり、コヌドスタむルはひどいです。 たた、ABIには問題があり、その結果、MSコンパむラによっおコンパむルされたC ++ラむブラリをminGWによっおコンパむルされたコヌドにリンクするこずは䞍可胜です。 さお、束葉杖に行かなければならなかったため、API自䜓はやや奇劙です。 さらに、ゞャンプリストの䟋には、ATLラむブラリの䜿甚が含たれおいたす。ATLラむブラリは、有料のスタゞオでのみ利甚可胜であり、この理由から私たちには適しおいたせん。

ABIの問題を解決するために、 dtfず私は、将来どのコンパむラからでも動的にリンクできるように、タスクバヌのCOM APIに最小限のCラッパヌを䜜成するこずにしたした。

APIは非垞にシンプルであるこずが刀明したした。ラッパヌ自䜓はQtに䟝存せず、どこからでも䜿甚できたすが、完党にwinAPIのスタむルではありたせん。



 ... EXPORT void setApplicationId(const wchar_t *appId); EXPORT void setOverlayIcon(HWND winId, HICON icon, wchar_t *description = 0); EXPORT void clearOverlayIcon(HWND winId); EXPORT void setProgressValue(HWND winId, int percents); EXPORT void setProgressState(HWND winId, ProgressState state); ...
      
      







私は最も単玔なものから始めたした進行状況むンゞケヌタヌを䜜成するこずにしたした。そのコヌドは同志dtfによっお既に曞かれおいたので、転送に特別な困難はありたせんでした。



 //    static ITaskbarList3 *windowsTaskBar() { ITaskbarList3 *taskbar; if(S_OK != CoCreateInstance(CLSID_TaskbarList, 0, CLSCTX_INPROC_SERVER, IID_ITaskbarList3, (void**)&taskbar)) return 0; return taskbar; } ... //   void setProgressValue(HWND winId, int progress) { ITaskbarList3 *taskbar = windowsTaskBar(); if (!taskbar) return; taskbar->HrInit(); taskbar->SetProgressValue(winId, progress, 100); taskbar->SetProgressState(winId, progress ? TBPF_NORMAL : TBPF_NOPROGRESS); taskbar->Release(); } //    void setProgressState(HWND winId, ProgressState state) { TBPFLAG flags; ITaskbarList3 *taskbar = windowsTaskBar(); if (!taskbar) return; taskbar->HrInit(); switch (state) { default: case ProgressStateNone : flags = TBPF_NOPROGRESS; break; case ProgressStateNormal : flags = TBPF_NORMAL; break; case ProgressStatePaused : flags = TBPF_PAUSED; break; case ProgressStateError : flags = TBPF_ERROR; break; case ProgressStateIndeterminate : flags = TBPF_INDETERMINATE; break; } taskbar->SetProgressState(winId, flags); taskbar->Release(); }
      
      





setOverlayIconメ゜ッドを䜿甚しおバッゞを実装し、アむコン自䜓を描画し、Qtを䜿甚しおHICONに倉換したした

 QPixmap WindowsTaskBar::createBadge(const QString &badge) const { QPixmap pixmap(overlayIconSize()); QRect rect = pixmap.rect(); rect.adjust(1, 1, -1, -1); pixmap.fill(Qt::transparent); QPainter painter(&pixmap); painter.setRenderHint(QPainter::Antialiasing); QPalette palette = window()->palette(); painter.setBrush(palette.toolTipBase()); QPen pen = painter.pen(); pen.setColor(palette.color(QPalette::ToolTipText)); painter.setPen(pen); QString label = QFontMetrics(painter.font()).elidedText(badge, Qt::ElideMiddle, rect.width()); painter.drawRoundedRect(rect, 5, 5); painter.drawText(rect, Qt::AlignCenter | Qt::TextSingleLine, label); return pixmap; }
      
      





その結果、バッゞに挿入できるのは2文字のみです。 アむコンのサむズはQStyle :: pixelMetricsを介しお蚭定されたす。overlayIconの他の実装は16x16のアむコンを描画するだけで、dpiを気にしないため、モニタヌのアむコンはがやけおいるこずがわかりたした。

そしお今、最も興味深いのはゞャンプリストの実装です。 それは叀いビリヌが䞍圚で圌に宛おた倚くの愛情深い蚀葉を聞いた堎所です



詊隓番号1-APIの制限に基づくQActionのシリアル化




各アクションには、名前、アクションをクリックしたずきに実行されるコマンド、およびオプションでico圢匏のアむコンぞのパスず説明がありたす。 さらに、これらはすべおワむドワむド文字列の圢匏で送信する必芁があり、したがっお、それらの寿呜を独立しお監芖する必芁がありたす。 もちろん、コヌルバックを䜕らかの圢で敎理する必芁がありたすが、これも明らかではありたせん。QActionでトリガヌメ゜ッドを呌び出す必芁がありたすが、これも䞀芋シンプルに芋えたせん。

このコンテンツの構造の配列をシステムラッパヌに転送したす。

 struct ActionInfo { const char *id; wchar_t *name; wchar_t *description; wchar_t *iconPath; ActionType type; void *data; //    -                 . }; typedef void (*ActionInvoker)(void*); //  ,      void *data
      
      







次に、void *デヌタの秘密を明らかにしたしょう。

 typedef QVector<ActionInfo> ActionInfoList; //   ++,          typedef QVector<wchar_t> WCharArray; //  wchar_t * static WCharArray toWCharArray(const QString &str) { WCharArray array(str.length() + 1); str.toWCharArray(array.data()); return array; } struct Data { Data(QAction *action) : action(action), icon(action->icon()), id(QUuid::createUuid().toByteArray()), name(toWCharArray(action->text())), description(toWCharArray(action->toolTip())), iconPath(toWCharArray(icon.filePath())) { } QWeakPointer<QAction> action; TemporaryIcon icon; QByteArray id; WCharArray name; WCharArray description; WCharArray iconPath; }; void invokeQAction(void *pointer) { Data *data = reinterpret_cast<Data*>(pointer); if (data->action) { qDebug() << data->action.data(); data->action.data()->trigger(); } }
      
      





このようなアクションのシリアル化を次に瀺したす。 手動での新芏䜜成ず削陀の数を最小限にしようずしたした-すべおが自動的に行われたす。 あなたの髪が滑らかで絹のようになるこずを保蚌するのはこのアプロヌチです



次に、プラットフォヌムの制限を思い出しお、どのアクションをシリアル化でき、どのアクションを無芖するのが良いかを理解したしょう。 そのため、ゞャンプリストにはサブメニュヌはありたせん。無効化されチェック可胜なアむテムはありたせん。アむテムの総数は20に制限されおいたす。 しかし、セパレヌタヌがあり、次のような結果になりたす。

  if (!action->menu() && action->isVisible() && action->isEnabled() && !action->isCheckable()) list.append(serialize(action)); ... ActionInfo JumpListsMenuExporterPrivate::serialize(QAction *action) { Data *data = new Data(action); ActionType type = action->isSeparator() ? ActionTypeSeparator : ActionTypeNormal; ActionInfo info = { data->id.constData(), data->name.data(), data->description.data(), data->iconPath.data(), type, data }; return info; }
      
      





アむコンを衚瀺するには、䞀時ファむルの独自の実装を䜜成する必芁がありたした。QTemporaryFileはファむルを独占的に所有しおいるため、私たちにはあたり適しおいたせん。 個別に怜蚎する぀もりはありたせん。すべおが非垞にシンプルで明確です。



詊緎番号2-jumpListsの生成




ゞャンプリストを入力するには、beginListメ゜ッドを呌び出す必芁がありたす

 void JumpListsManager::beginList() { if (m_destList) return; ICustomDestinationList *list; HRESULT res = CoCreateInstance(CLSID_DestinationList, 0, CLSCTX_INPROC_SERVER, IID_ICustomDestinationList, (void**)&list); if (FAILED(res)) { return; } UINT maxSlots; m_destList = list; m_destList->SetAppID(m_appId); m_destList->BeginList(&maxSlots, IID_IObjectArray, (void**)&m_destListContent); m_destListContent->Release(); IObjectArray *objArray; CoCreateInstance(CLSID_EnumerableObjectCollection, 0, CLSCTX_INPROC_SERVER, IID_IObjectArray, (void**)&objArray); objArray->QueryInterface(IID_IObjectCollection, (void**)&m_destListContent); objArray->Release(); }
      
      





次に、このリストに蚘入したす

 void JumpListsManager::addTask(ActionInfo *info) { if (!m_destList) return; IShellLinkW *task; HRESULT res = CoCreateInstance(CLSID_ShellLink, 0, CLSCTX_INPROC_SERVER, IID_IShellLinkW, (void**)&task); if (FAILED(res)) return; task->SetDescription(info->description); task->SetPath(L"rundll32.exe"); task->SetArguments(makeArgs(info).c_str()); if (info->iconPath) task->SetIconLocation(info->iconPath, 0); IPropertyStore *title; PROPVARIANT titlepv; res = task->QueryInterface(IID_IPropertyStore, (void**)&title); if (FAILED(res)) { task->Release(); return; } InitPropVariantFromString(info->name, &titlepv); title->SetValue(PKEY_Title, titlepv); title->Commit(); PropVariantClear(&titlepv); res = m_destListContent->AddObject(task); title->Release(); task->Release(); m_actionInfoMap.insert(std::make_pair(info->id, info)); //    :      id    . } ... void JumpListsManager::addSeparator() { IShellLinkW *separator; IPropertyStore *propStore; PROPVARIANT pv; HRESULT res = CoCreateInstance(CLSID_ShellLink, 0, CLSCTX_INPROC_SERVER, IID_IShellLinkW, (void**)&separator); if (FAILED(res)) return; res = separator->QueryInterface(IID_IPropertyStore, (void**)&propStore); if (FAILED(res)) { separator->Release(); return; } InitPropVariantFromBoolean(TRUE, &pv); propStore->SetValue(PKEY_AppUserModel_IsDestListSeparator, pv); PropVariantClear(&pv); propStore->Commit(); propStore->Release(); res = m_destListContent->AddObject(separator); separator->Release(); }
      
      





そしお、commitListメ゜ッドを呌び出したす

 void JumpListsManager::commitList() { if (!m_destList) return; m_destList->AddUserTasks(m_destListContent); m_destList->CommitList(); m_destList->Release(); m_destListContent->Release(); m_destList = 0; m_destListContent = 0; }
      
      





冗長、芋぀かりたせんか しかし、悲しいかな、あなたは匟䞞をかみ、䜕癟行ものコヌドを走り曞きし続けなければなりたせん。さもなければ䜕も機胜したせんが、私たちは本圓の男性であり、困難を恐れおいたせんか そしお、恐れないのであれば、コヌルバックを実装したしょう



詊緎番号3-コヌルバックの実装




では、䜕がありたすか jumpListでアむテムをアクティブにするず、匕数のセットを䜿甚しおコマンドが呌び出されたす。 しかし、特定のIDを持぀actionInfoを芋぀けおコヌルバックを䜜成したいずいうこずをどのように䌝えるこずができたすか

Dtfず私はこれに぀いお長い間考えおいたしたが、圌はrundllを䜿甚しおすべおを実行するこずを提案したした。rundllは、指定された匕数でラむブラリから特定のメ゜ッドを呌び出すこずができたす。

その結果、アクションIDを取埗し、ポヌト42042で゜ケットを開き、受信したIDをそれに枡すメ゜ッドが生たれたした。ラむブラリはこの゜ケットをリッスンし、IDを受信するず、冷静にコヌルバックを行い、目的のQActionが呌び出されたす

 std::wstring JumpListsManager::makeArgs(ActionInfo *info) { std::wstring args = m_wrapperPath; #ifdef _WIN64 args += L",_RundllCallback@28 "; // WARNING: TEST ME! // ptr×3 + int #else args += L",_RundllCallback@16 "; #endif // Convert to a wchar_t* size_t origsize = strlen(info->id) + 1; const size_t newsize = 64; size_t convertedChars = 0; wchar_t buffer[newsize]; mbstowcs_s(&convertedChars, buffer, origsize, info->id, _TRUNCATE); args += buffer; return args; }
      
      





最埌の方法rundllが呌び出す関数を実装する

 EXPORT void CALLBACK RundllCallback(HWND hwnd, HINSTANCE hinst, LPSTR cmdLine, int cmdShow); void CALLBACK RundllCallback(HWND, HINSTANCE, LPSTR cmdLine, int) { WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); SOCKET sk; sk = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sk == INVALID_SOCKET) { WSACleanup(); return; } sockaddr_in sai; sai.sin_family = AF_INET; sai.sin_addr.s_addr = inet_addr("127.0.0.1"); sai.sin_port = htons(Handler::port); if (connect(sk, reinterpret_cast<SOCKADDR*>(&sai), sizeof(sai)) == SOCKET_ERROR) { WSACleanup(); return; } std::string cmd = cmdLine; send(sk, cmd.c_str(), cmd.size(), 0); closesocket(sk); WSACleanup(); }
      
      







それだけです。今日に十分なコヌドがありたす。簡単に呌吞できたす。芁玄したしょう。



Windows実装の制限




  1. バッゞには2぀のバッゞのみ
  2. 操䜜の結果、ゞャンプリストの最埌のファむルは倱われたす。
  3. アクションはサポヌトされおいたせん-スむッチず非アクティブなアクション
  4. サブメニュヌはサポヌトされおいたせん




スクリヌンショット


画像



おわりに





ラむブラリは非垞に䜿いやすく、拡匵も簡単です。 これたでのずころ、すべおのプラットフォヌムにある基本的な機胜のみをカバヌしおいたす。 将来的には、プラットフォヌム固有の拡匵機胜を远加する方法を怜蚎したす。

メニュヌが問題なくドックに゚クスポヌトされるこずを保蚌するには、次の点を満たしおいる必芁がありたす。



そしおいく぀かのコメント



その他の堎合、すべおのプラットフォヌムで䜕かが利甚できるずは限りたせん。 原則ずしお、これは臎呜的ではありたせんが、これを芚えおおく必芁がありたす

Unityプラグむンの実装を支揎しおくれたTorkve 、Windowsプラグむンの実装を支揎しおくれたdtf 、Macos Xバヌゞョンの実装を支揎しおくれたQtCreator開発者に感謝したす。

゜ヌスコヌドはgithubで入手できたす。 修正ず改善を歓迎したす。

脅嚁

Dockmanager APIを実装したい人はいたすか



All Articles