疑似TweetDeck
TweetDeckのバージョンが新しくなり、有料化された。どうやら新バージョンのTweetDeckでも複数アカウント表示はできるらしく、しかも1アカウントだけ課金すれば十分という情報もあるが、ここでは課金せずに複数アカウントを並べる方法を紹介する。といっても技術的には何も興味深いことはなく、単に別プロファイルでブラウザを複数起動してアカウントを使い分けるのを多少やりやすくしたという程度である。
具体的には、まずウインドウスタイルと表示領域の変更によってタイトルバーなど無駄な領域を削減し、自動でサイズと位置を調整して画面上に同じサイズで綺麗に並ぶようにした。さらに、ショートカットキーを用いて全ウインドウ一度に表示(Ctrl+Alt+Shift+.
)/非表示(Ctrl+Alt+Shift+,
)の切り替えができるようにした。
実装にはAutoHotkeyを用いた(Windowsのみで動作確認)。スクリプト例ではChromeを使用しているが、単にウインドウを等間隔に並べて表示/非表示を切り替えるだけなので、何のソフトでも構わない(ただしタイトルバーでのウインドウの検出などは普通にできる必要がある)。自動リロード機能なども特に実装していない。(単にブラウザを動かしているだけなので拡張機能など使えばよい)
- chromeを起動するところの
--app-id=jgeocpdicgmkeemopbanhokmhcgcflmi
の部分についてはWebブラウザ(Chrome)のPWAのところを参照。
; ウインドウのアクティベート時に強引な方法を用いる(FlashWindowだけで終わってしまうのを避ける)? #WinActivateForce ; Windowsのディスプレイのworkarea(ディスプレイからタスクバーの部分を除いたもの)を縦にn分割し、n個のウインドウを並べる。 ; Windowsでは、Region指定によりウインドウの一部の長方形領域だけを表示することができるので、これを利用して画面の無駄な余白を削る。 ; ただし通常のウインドウスタイルだと不自然な表示になることがあるので、ボーダーレスウインドウ(0x00000080、WS_EX_TOOLWINDOW)を指定している。 ; 概念的には、3種類の領域を想定している。狭いほうから順に、 ; 1. メイン領域…そのウインドウの「持ち分」であると考えられるメインの表示領域。これが互いに被らないように画面をn分割する。 ; 2. 可視領域…ウインドウが実際に画面上で表示されているサイズ。つまり、各ウインドウは、ディスプレイの端や他のウインドウの上に少しずつはみ出して表示されている。(隙間から背景をクリックしてしまうことがなくなり、動作が安定する) ; 3. ウインドウ領域…Windowsでの内部的なウインドウの領域。Region指定により、一部は不可視になっている。 ; 以下では、ウインドウ領域に入っているが可視領域に入っていない部分を「不可視領域」、可視領域に入っているがメイン領域に入っていない領域を「オーバーラップ領域」と呼ぶ。 ; 左右の不可視領域の幅 Global off_wid_sum := 10 + 5 ; 上部の不可視領域の幅(タイトルバーがあるので大きめ)(下部には不可視領域を設定していない) Global off_top_add = 30 ; 上下と左右のオーバーラップ領域 Global off_wid_overlap = 4 Global off_hei_overlap = 8 ; タスクバーの位置とサイズを取得 SysGet, TaskBar, MonitorWorkArea, 0 Global num_of_decks := 3 ; メイン領域 Global Width_org := (TaskBarRight-TaskBarLeft) // num_of_decks Global Height_org := TaskBarBottom-TaskBarTop ; 可視領域 Global Width_3 := Width_org+off_wid_overlap*2 Global MyHeight := Height_org+off_hei_overlap*2 ; ウインドウを移動する際に、上にスナップした扱いにならないための安全な距離 Global safe_dist_border = 10 ; 角にスナップした扱いにならないように、(横方向の)中央付近をつかむ Global safe_dist_wid := Width_3 / 2 ; この文字列で対象アプリケーションの起動を検出 Global TitleStr := "Twitter" SetBorderlessWindow(ByRef this_id) { ; 非クライアント領域のレンダリングを無効に DllCall("dwmapi\DwmSetWindowAttribute", "ptr", this_id, "uint", DWMWA_NCRENDERING_POLICY := 2, "int*", DWMNCRP_DISABLED := 1, "uint", 4) ;これらは役に立たない ;WinSet, Style, -0x80000000, ahk_id %this_id% ;WinSet, Style, -0x00020000, ahk_id %this_id% ;WinSet, Style, -0x00040000, ahk_id %this_id% WinSet, ExStyle, 0x00000080, ahk_id %this_id% ; 可視領域の設定 WinSet, Region, W%Width_3% H%MyHeight% %off_wid_sum%-%off_top_add%, ahk_id %this_id% } ; 与えられた座標にメイン領域を設定する関数 PlaceWindowAtPos(ByRef this_id, x, y) { ; ウインドウサイズの設定。負の座標には直接は動かせない(動かすと、強制的に違うサイズにされてしまった)ので、一旦正の座標に動かしてからマウスで移動 WinMove, ahk_id %this_id%,, x, y, Width_3 + off_wid_sum*2, MyHeight+off_top_add ; SetBorderlessWindow(this_id) WinSet, Redraw, , ahk_id %this_id% Sleep, 300 MouseClickDrag, LEFT, safe_dist_wid + safe_dist_border + off_wid_sum + off_wid_overlap, safe_dist_border + off_top_add + off_hei_overlap, safe_dist_wid + safe_dist_border, safe_dist_border, 5 } ; メイン部分 ; TitleStrがタイトルになっている既存のウインドウがあれば無視したいので、配列に格納 MyTitleHandles := [] WinGet, windows, List, %TitleStr% Loop, %windows% { this_id := windows%A_Index% MyTitleHandles[this_id] := true } ProfileNames:=["""Default""", """Profile 6""" ,"""Profile 5"""] Wins:=[] Loop, %num_of_decks% { ; Twitterの起動 win_index := A_Index Prof := ProfileNames[A_Index] ; idはtwitter.comが提供するマニフェストからchromeが計算した結果必ずこれになる Run, "C:\Program Files\Google\Chrome\Application\chrome_proxy.exe" --profile-directory=%Prof% --app-id=jgeocpdicgmkeemopbanhokmhcgcflmi Loop, { Sleep, 222 WinGet, newWindows, List, %TitleStr% Loop, %newWindows% { this_id := newWindows%A_Index% if (!MyTitleHandles.HasKey(this_id)) { Sleep, 300 ; Indexに応じたメイン領域を設定 PlaceWindowAtPos(this_id, (win_index-1)*Width_org, TaskBarTop) MyTitleHandles[this_id] := true Wins[win_index] := this_id Goto, NEX } } } NEX: } OnExit, ExitSub ; 開始処理はここで終了 return ; 常駐してショートカット処理 ^!+,:: ; Ctrl+Alt+Shift+, { Loop, %num_of_decks% { wintemp := Wins[A_Index] WinHide, ahk_id %wintemp% } } return ^!+.:: ; Ctrl+Alt+Shift+. { Loop, %num_of_decks% { wintemp := Wins[A_Index] WinShow, ahk_id %wintemp% } Sleep, 100 Loop, %num_of_decks% { wintemp := Wins[A_Index] WinActivate ahk_id %wintemp% SetBorderlessWindow(wintemp) } } return ; 新しいインスタンスによって置換される際には(←これに限定しなくてもいいかもしれないが…)ウインドウを閉じる ExitSub: If A_ExitReason = Single Loop, %num_of_decks% { wintemp := Wins[A_Index] WinClose ahk_id %wintemp% } ExitApp
- 必要に応じて、後述の狭い画面向けのCSSと併用するとよい。
- 需要があれば、画面を縦だけでなく横に分割するなどの機能拡張を実装するかもしれない。
- Vivaldiのタイリング機能を使って、複数タブを横並びで表示することもできるようだが、タブ間でセッションが共有されているので複数のアカウントに同時にログインしてTLを並べて表示することはできない(単一アカウントのTLと通知を並べるなどは可)。
- Stack browser(無料版だと機能制限あり?)など、複数セッションを並べて表示できるサービスもあるようなので、それでもよいかも
狭い画面向けのTwitterのCSS
StylusなどのCSSを適用してくれる拡張機能をインストールし、以下を入力する。コメントで簡単に説明を書いたので必要に応じて数値の変更やコメントアウトなどするとよい
- Stylusに類似のものでStylishというのがあったが、個人情報の収集かなんかで問題になったので使わない方が良さそう
/*一応参考(ただし結局ほぼ使わず) https://polygonote.com/2022_0308_18019/ */ /* メインエリアの幅の制約を解除 */ main { width: 100%; } /*最大幅の制約を解除*/ .r-1ye8kvj { max-width: 6000px; } /*比較的広いときの幅の制約の解除*/ .r-rthrr5 { width: 100%; } /*やや狭いときの幅の制約の解除*/ .r-1obr2lp { width: 100%; } /*狭いときの幅の制約の解除*/ .r-33ulu8 { width: 100%; } /*トレンドエリア非表示*/ div[data-testid="sidebarColumn"]{ display: none; } /*参考記事にあったが、不要そう .r-o96wvk { width: 100%; } */ /*左側バナーのサイズを常に狭くする(オリジナルのサイトで最も狭くしたときの値は68で、ここからpadding-leftとpadding-rightで8ずつ引くと52になる)*/ header[role="banner"] > div, header[role="banner"] > div > div > div { width:52px } /*header[role="banner"], こっちは不要?*/ header[role="banner"] > div > div > div > div > div > a/*「ツイートする」ボタンのサイズ*/ { max-width: 100%; } /*「ツイートする」ボタンのサイズ*/ .r-1dye5f7 { padding-left: 0px; padding-right: 0px; } /*幅が広いときでも各種バナーアイコンを常に中央揃え*/ header[role="banner"] > div > div > div > div > div, header .r-1habvwh { /*不要? width: 100%; */ -webkit-align-items: center; -webkit-box-align: center; align-items: center; -ms-flex-align: center; } /*幅が広いときでもバナーの各種アイコンの横に「通知」などの文字は出さない*/ header > div > div > div > div.css-1dbjc4n.r-usiww2 > div > div > div.css-1dbjc4n.r-1wbh5a2.r-dnmrzs.r-1ny4l3l, header > div > div > div > div.css-1dbjc4n.r-usiww2 > div > div > div.css-1dbjc4n.r-obd0qt.r-16y2uox, nav > div > div > div.css-901oao.css-1hf3ou5.r-18jsvk2.r-1tl8opc.r-adyw6z.r-135wba7.r-1joea0r.r-88pszg.r-bcqeeo.r-qvutc0, nav > a > div > div.css-901oao.css-1hf3ou5.r-18jsvk2.r-1tl8opc.r-adyw6z.r-135wba7.r-1joea0r.r-88pszg.r-bcqeeo.r-qvutc0{ display:none } /*画像サイズ調整*/ /*div.css-1dbjc4n.r-1ets6dv.r-1867qdf.r-rs99b7.r-1loqt21.r-adacv.r-1ny4l3l.r-1udh08x.r-o7ynqc.r-6416eg > div > div.css-1dbjc4n.r-14gqq1x > div, /*引用ツイート内画像専用。不要*/ /*ツイート画像・動画。しかし一行下のもののほうが動画を再生時のみ枠に収めて最大化ボタンを押せるようにできるので、こちらは不要そう。 article[data-testid="tweet"] > div > div > div.css-1dbjc4n.r-18u37iz > div.css-1dbjc4n.r-1iusvr4.r-16y2uox.r-1777fci.r-kzbkwu > div.css-1dbjc4n.r-1ssbvtb.r-1s2bzr4 > div.r-9aw3ui > div > div > div,*/ article[data-testid="tweet"] > div > div > div.css-1dbjc4n.r-18u37iz > div.css-1dbjc4n.r-1iusvr4.r-16y2uox.r-1777fci.r-kzbkwu > div.css-1dbjc4n.r-1ssbvtb.r-1s2bzr4 > div.r-9aw3ui > div > div > div > div,/*再生時の動画コンテンツ。サムネイル状態だとうまく適用外になってくれる*/ /*引用ツイート画像・動画。しかし一行下のもののほうが動画を再生時のみ枠に収めて最大化ボタンを押せるようにできるので、こちらは不要そう。 article[data-testid="tweet"] > div > div > div.css-1dbjc4n.r-18u37iz > div.css-1dbjc4n.r-1iusvr4.r-16y2uox.r-1777fci.r-kzbkwu > div.css-1dbjc4n.r-1ssbvtb.r-1s2bzr4 > div > div > div > div.r-14gqq1x > div,/*引用ツイートの画像・動画*/ article[data-testid="tweet"] > div > div > div.css-1dbjc4n.r-18u37iz > div.css-1dbjc4n.r-1iusvr4.r-16y2uox.r-1777fci.r-kzbkwu > div.css-1dbjc4n.r-1ssbvtb.r-1s2bzr4 > div > div > div > div.r-14gqq1x > div> div> div,/*同上。*/ /*以下2つは、それぞれの子要素のdivに適用するよりこのほうがうまくいった*/ div[data-testid="card.wrapper"] > div[data-testid="card.layoutLarge.media"] > a,/*埋め込み記事の画像*/ div[data-testid="card.wrapper"] > div[data-testid="card.layoutLarge.media"] > div/*埋め込み記事の動画。サイトによっては画像も引っかかる?*/ { max-height: 200px; max-width: 500px; justify-content: center; overflow:hidden; } /*画像の余白の色を枠線と合わせる*/ div[data-testid="card.wrapper"] > div[data-testid="card.layoutLarge.media"], article[data-testid="tweet"] > div > div > div.css-1dbjc4n.r-18u37iz > div.css-1dbjc4n.r-1iusvr4.r-16y2uox.r-1777fci.r-kzbkwu > div.css-1dbjc4n.r-1ssbvtb.r-1s2bzr4 > div > div > div > div.r-14gqq1x { background-color:rgb(232, 232, 232) } div[data-testid="card.wrapper"] > div[data-testid="card.layoutLarge.media"] > div >div[style="padding-bottom: 100%;"]/*埋め込み記事の動画*/ { /*不要? display:none*/ } /*「ホーム」ではなくツイート単独ページ(https://twitter.com/###/status/#########?s=20)用。nth-child(3)がないとTL上の画像にも適用されてしまう*/ article[data-testid="tweet"] > div > div > div:nth-child(3) > div > div.css-1dbjc4n.r-1ssbvtb.r-1s2bzr4 > div > div > div > div { max-width: 800px; } /*ツイートの枠線の色を濃くする*/ .r-j5o65s { border-bottom-color: rgb(150, 150, 150); } .r-jxzhtn { border-color: rgb(150, 150, 150); } /* フォントサイズと行間 */ .r-a023e6 { font-size: 15px; } .r-rjixqe { line-height: 17px; } /*参考記事にあったが、不要? .r-1i10wst { font-size: 15px; } */ /* ツイート入力エリアの文字高さ。そのままで良さそう。 .r-135wba7 { line-height: 20px; } */ /*「ホーム」の文字を中央揃え*/ main > div > div > div > div > div > div:first-child > div:first-child > div:first-child > div > div > div > div > div .r-1habvwh { -webkit-align-items: center; } /*「ホーム」部分の長さ上限解除(広いとき)*/ .r-sb58tz { max-width: 100%; } /*入力欄の幅が変わってしまうので、固定*/ .r-1pjcn9w { max-width: 600px; } /*サイドバーのアイコンエリアの余白削る*/ .r-1pn2ns4 { padding-left: 0px; padding-right:0px; } /*サイドバーの各アイコンの余白も削る*/ .r-xyw6el { padding-left: 0px; padding-right: 0px; } /*「ホーム」「おすすめ」「通知」など狭く*/ .r-1h3ijdo { height: 30px; } /*「おすすめ」「フォロー中」の青い線が見えるように*/ .r-95jzfe { padding-top: 8px; } .r-1l7z4oj { padding-bottom: 8px; } /*ツイートの上部分を削る*/ .r-ttdzmv { padding-top: 4px; } /*ツイートの下部分、つまり「いいね」「リツイート」アイコンの下を削る*/ .r-kzbkwu { padding-bottom: 4px; } /*上記アイコンの上も削る*/ .r-1s2bzr4 { margin-top: 4px; } /* 何のために入れたんだっけ…? .r-ttdzmv.r-ero68b.r-nf76sl, .r-kzbkwu.r-ero68b.r-nf76sl { padding-bottom: 12px; padding-top: 12px; }*/ /*検索ボックスの幅を小さく*/ input[data-testid="SearchBox_Search_Input"] { padding-top: 0px; padding-bottom: 0px; }