Codeer.Friendly で WPF の ContextMenu を取得する方法

Codeer.Friendly とは

Friendly は 株式会社 Codeer が開発した WindowsGUI を自動テストするためのライブラリです。

DLLインジェクションという方法で自動化するため、アプリケーションの内部のコードも操作でき、他の自動化ツールではできないような細やかな操作が可能となるため重宝しています。ただ、privateメソッドすら実行可能というある意味恐ろしいツールなので用法・用量を守って正しく使わないとえらいことになります。

Friendly の使い方については他の方のブログを見ていただくとして、今回は ContextMenu の取得で手間取ったので備忘録として記事を書きます。

ContextMenu の取得

ContextMenu を VisualTree() を使って取り出そうとして取り出せません。 Friendly では VisualTreeWithPopup() というのが別に用意されていてこちらを使えばContextMenuを取り出せます。 なぜ別々のメソッドになっているのかは謎ですが、これでいけます。

Popup を取得するときは VisualTree() でも VisualTreeWithPopup() でも取り出せるので好きなほうを使いましょう。

var process = Process.Start(@"C:\dev\source\repos\WpfApp3\WpfApp3\bin\Debug\net8.0-windows\WpfApp3.exe");
Thread.Sleep(1000);
var app = new WindowsAppFriend(process);
var shell = app.WaitForIdentifyFromTypeFullName("WpfApp3.MainWindow");
var contextMenus = shell.VisualTreeWithPopup().ByType("System.Windows.Controls.ContextMenu");
var contextMenu = contextMenus[0];
var menuitems = contextMenu.VisualTree().ByType("System.Windows.Controls.MenuItem");
var menuitem = new WPFMenuItem(menuitems[2]);
menuitem.EmulateClick();

動的にコンポーネントを切り替える方法

コンポーネントのオブジェクトの状態によって子コンポーネントを動的に切り替える。かつ、親コンポーネントのマウスイベントから子コンポーネントのメソッドを呼び出す。

useRecoil を使うのがミソか。

import {
  useEffect,
  useState,
  forwardRef,
  useRef,
  useImperativeHandle,
  Children,
} from "react";
import { useRecoilState } from "recoil";
import { flagState, positionState } from "./state.jsx";

const Klass1 = forwardRef((props, ref) => {
  const [position, setPosition] = useRecoilState(positionState);

  useImperativeHandle(ref, () => ({
    updatePosition,
  }));

  const updatePosition = (event) => {
    setPosition({
      x: event.clientX,
      y: event.clientY,
    });
  };

  return <>{props.children}</>;
});

const Klass2 = forwardRef((props, ref) => {
  const [position, setPosition] = useRecoilState(positionState);

  useImperativeHandle(ref, () => ({
    updatePosition,
  }));

  const updatePosition = (event) => {
    setPosition((prev) => {
      return {
        x: prev.x + 1,
        y: prev.y + 1,
      };
    });
  };

  return <>{props.children}</>;
});

const Factory = ({ flag, children, childRef }) => {
  if (flag) {
    return <Klass1 ref={childRef}>{children}</Klass1>;
  } else {
    return <Klass2 ref={childRef}>{children}</Klass2>;
  }
};

const MyComponents = () => {
  const [flag, setFlag] = useRecoilState(flagState);
  const [position, setPostion] = useRecoilState(positionState);

  const childRef = useRef();

  const style = {
    width: 120,
    height: 120,
    backgroundColor: "green",
  };

  return (
    <Factory flag={flag} childRef={childRef}>
      <div
        id="my_components"
        style={style}
        onMouseMove={(event) => childRef.current.updatePosition(event)}
      >
        <label>
          x={position.x}, y={position.y}
        </label>
      </div>
    </Factory>
  );
};

export default MyComponents;

WindowsでKernel#spawnで作った子プロセスを親プロセスが死ぬときに同時に死んでもらう

WindowsだとRubyのspawnメソッドでプロセスを作成すると親プロセスからは切り離されて、親プロセスが終了しても残り続けます。

これが便利な時もありますが、私のユースケースでは不便に働くことがありました。なので、今回は親子共々死んでもらいます。

以前に、「アプリケーションが終了するときに、子プロセスも終了させる方法」ということで、Go言語で実装した記事を投稿しました。

bamch0h.hatenablog.com

こちらの焼き増し記事となります。ご了承を。

Ruby コード

module Kernel32
    require 'fiddle'
    require 'fiddle/import'
    require 'fiddle/types'
    
    extend Fiddle::Importer
    dlload 'kernel32.dll'
    include Fiddle::Win32Types

    typealias 'ULONG_PTR', 'ULONG*'
    typealias 'LONGLONG', 'double'
    typealias 'SIZE_T', 'ULONG_PTR'
    typealias 'ULONGLONG', 'unsigned long long'
    typealias 'LPVOID', '*void'

    SecurityAttributes = ([
        "DWORD nLength",
        "LPVOID lpSecurityDescriptor",
        "BOOL bInheritHandle",
    ])

    typealias 'LPSECURITY_ATTRIBUTES', '*SecurityAttributes'

    LARGE_INTEGER = struct([
        "LONGLONG QuadPart"
    ])

    IoCounters = struct([
        "ULONGLONG ReadOperationCount",
        "ULONGLONG WriteOperationCount",
        "ULONGLONG OtherOperationCount",
        "ULONGLONG ReadTransferCount",
        "ULONGLONG WriteTransferCount",
        "ULONGLONG OtherTransferCount",
    ])

    JobObjectBasicLimitInformation = struct([
        { PerProcessUserTimeLimit: LARGE_INTEGER},
        { PerJobUserTimeLimit: LARGE_INTEGER},
        'DWORD LimitFlags',
        'SIZE_T MinimumWorkingSetSize',
        'SIZE_T MaximumWorkingSetSize',
        'DWORD ActiveProcessLimit',
        'ULONG_PTR Affinity',
        'DWORD PriorityClass',
        'DWORD SchedulingClass'
    ])

    JobObjectExtendedLimitInformation = struct([
        { BasicLimitInformation: JobObjectBasicLimitInformation},
        { IoInfo: IoCounters },
        'SIZE_T ProcessMemoryLimit',
        'SIZE_T JobMemoryLimit',
        'SIZE_T PeakProcessMemoryUsed',
        'SIZE_T PeakJobMemoryUsed',
    ])

    JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x00002000

    extern 'HANDLE CreateJobObjectA(LPSECURITY_ATTRIBUTES, LPCSTR)'
    extern 'HANDLE OpenProcess(DWORD, BOOL, DWORD)'
    extern 'BOOL SetInformationJobObject(HANDLE, int, LPVOID, DWORD)'
    extern 'BOOL AssignProcessToJobObject(HANDLE, HANDLE)'
    extern 'BOOL CloseHandle(HANDLE)'
end

class JobObject
    PROCESS_SET_QUOTA = 0x0100
    PROCESS_TERMINATE = 0x0001
    JobObjectExtendedLimitInformation = 9
    
    def initialize
        @job = Kernel32.CreateJobObjectA(0, 0)

        info = Kernel32::JobObjectExtendedLimitInformation.malloc
        
        info.BasicLimitInformation.LimitFlags = Kernel32::JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
        
        Kernel32.SetInformationJobObject(
            @job,
            JobObjectExtendedLimitInformation,
            info,
            Fiddle::Importer.sizeof(Kernel32::JobObjectExtendedLimitInformation)
        )
    end

    def register(pid)
        hProcess = Kernel32.OpenProcess(PROCESS_SET_QUOTA | PROCESS_TERMINATE, 0, pid)
        Kernel32.AssignProcessToJobObject(@job, hProcess)    
        Kernel32.CloseHandle(hProcess)
    end
end

job_obj = JobObject.new
pids = []
3.times do |i|
    pid = spawn("ruby.exe -e 'loop { p #{i}; sleep 1}'")
    job_obj.register(pid)
end

sleep 5

上記コードを実行すると、rubyのプロセスが3つ起動します。これらプロセスは無限ループで動作しているので通常はCTRL-Cで殺すか、taskkill等で殺すかしないと終了しません。ですが今回は親プロセスが5秒後に終了すると同時に終了します。

苦労した点

Goの場合は、すでにWin32APIのライブラリがあり、それを使えば比較的簡単に実装できましたが、Rubyの場合はそのようなライブラリがなくAPIの定義からする必要がありました。

Rubyには fiddle という ffi を行うための標準ライブラリがあり、それを用いてAPI定義を作成していくのですが、如何せん fiddle の情報が少なく「こういった場合はどうやって定義するんだろう?」という状況が結構起こった感覚があります。

例えば、Windowsでは LONGLONGという型がありますが、定義は以下のようになっているようです。

#if !defined(_M_IX86)
 typedef __int64 LONGLONG; 
#else
 typedef double LONGLONG;
#endif

なので、今動かしているプラットフォームに依存しているということなのですが、これを Ruby で表現する方法がわかりませんでした。おそらく私の環境では double で定義しておけば問題ないだろう。ということで今回のケースでは LONGLONG は double で定義しました。

また、入れ子になっている構造体の定義もどのように定義すればいいか、公式のドキュメントからはわかりませんでした。(ちゃんと読み込んだら書いてあるのかもですが、私は見つけられませんでした)

じゃぁ、どうやって定義したかというと、GithubのPullRequestに例が載っていたのでその通りに実装して動くかどうか試して、動いたのでこれが正解かな?という感じで定義しました。

github.com

これはドキュメントにコントリビュートするチャンスかもしれませんね!!

制限事項

上記のコード例ははじめは notepad.exe で書いていたのですが、なぜか一つだけプロセスが残るという問題がありました。これははじめのメモ帳だけプロセスが二つ起動するため、pidが一つだけJobObjectに登録されないことが問題と考えていますが、それをどう解決できるのかがわからず今回での対応は保留とさせていただきました。どなたかわかる方いらっしゃったら教えてください!!

まとめ

親プロセスが死んだときに子プロセスも死ぬようにJobObjectに登録するRubyプログラムを書いてみました。Fiddle を使ったことがなかったので使い方等含めて勉強になったかなと思います。できればもう書きたくないですが、もし書くことがあればこの記事を参考にできると思うで、未来の自分へのメモ書きということでここに記しておきます。

以上!!

【読書感想ブログ】Go言語プログラミングエッセンス - mattn / 技術評論社

Go言語といえば mattnさん、mattnさんといえばGo言語。それくらいには mattnさんが書かれるGo言語の書籍には信頼がありますが、そんな mattnさんが新たに出版された書籍が「Go言語プログラミングエッセンス」です。しかも単著。これは買うしかないですよね。そして読むしかないですよね。なので読んでみた感想です。

はじめのほうには Go言語がなぜ作られたのか、どういう言語なのかというところ、または文法的なところが書かれていました。mattnさんはブログもされていて、たまにポストされるんですが、私でもわかるような書き口で書いてくださっていて助かるな~といつも思っているんですが、その書き口がこの書籍にも反映されていて、内容がスラスラ入ってくる感じになってます。

この書籍のいいところは、具体的なコードが多く書かれていて手を動かしながら理解できる点が良いと思いました。また、簡単なWebアプリケーションやCLI題材によく使われるパッケージの紹介等もされていてよかったです。

あとは、Go言語に限らないテクニックなんじゃないかな?と思う部分もあり、たとえば「推測するな計測せよ」なんていう思想はほかの言語でも通用しそうですよね。そういったプログラミングをする上での大事な考え方も学べて良い本だなぁ~と思いました。

個人的に今後に役立ちそうだなぁと思った部分の抜き書き

go get でバージョン指定

go get github.com/mattn/go-runewidth@v0.0.12 のようにするとバージョンを指定して go get できる

最新バージョンにバグがあったり、破壊的な変更がされていたりする時にこういう対応をするとよい

vendor ディレクト

go.mod と同じディレクトリにあるvendorというディレクトリに置かれたモジュールのソースコードは優先して参照されます

以下のコマンドを実行すると、go.mod と go.sum に書かれたモジュールが一括で vendor ディレクトリにダウンロードできます

$ go mod vendor

おまじない

おまじないとして次のようにインターフェースIの型を持つ変数_に型fooのポインタ型nilを代入しておくことで、わざわざコンパイルをしなくても、IDEがエラーを検知して間違いに気づくことができます

recoverの使いどころ

通常、意図しないpanicが起きうるということは、そのプログラムにはバグがあることを意味するため、不用意にrecoverを呼び出すべきではありません。プログラムは落ちるべくして落ちたほうがよく、それを止めるべきではありません。

Explicit is better than implicit, Simple is betetr than complex

明示的であることは暗黙的であることよりも優れている。単純であることは複雑であることよりも優れている

Goにおける日付時刻の書式

「1月2日3時4分5秒2006年」と連番になっており、おおよそ表4.1のように理解すると覚えやすくなっています。

書式 連番 意味
1 1
01 1 月のゼロ埋め
2 2
02 2 日のゼロ埋め
3 3
03 3 時のゼロ埋め
15 3 時のゼロ埋め(24時間表記)
4 4
04 4 分のゼロ埋め
5 5
05 5 秒のゼロ埋め
2006 6
+007 7 タイムゾーン
JST - タイムゾーン

timeパッケージに標準で用意されている書式

取り扱いづらそうに見えますが、多くの場合はtimeパッケージに標準で用意されている固定の書式名を使うことができます

書式名 フォーマット
Layout 01/02 03:04:05PM '06 -0700
ANSIC Mon Jan \_2 15:04:05 2006
UnixDate Mon Jan \_2 15:04:05 MST 2006
RubyDate Mon Jan 02 15:04:05 -0700 2006
RFC822 02 Jan 06 15:04 MST
RFC822Z 02 Jan 06 15:04 -0700
RFC850 Monday, 02-Jan-06 15:04:05 MST
RFC1123 Mon, 02 Jan 2006 15:04:05 MST
RFC1123Z Mon, 02 Jan 2006 15:04:05 -0700
RFC3339 2006-01-02T15:04:05Z07:00
RFC3339Nano 2006-01-02T15:04:05.999999999Z07:00
Kitchen 3:04PM
Stamp Jan \_2 15:04:05
StampMilli Jan \_2 15:04:05.000
StampMicro Jan \_2 15:04:05.000000
StampNano Jan \_2 15:04:05.000000000
DateTime 2006-01-02 15:04:05
DateOnly 2006-01-02
TimeOnly 15:04:05

time.ParseDuration

d, err := time.ParseDuration("3s")

3秒であれば time.Second * 3 と書くことができます。この 3s や 4m といった記法は、ユーザーから経過時間を指定してもらう際にとても便利で、例えばUNIXの sleep コマンドにも採用されています

context.Context

context.Context は習慣的に関数の第一引数として渡され、仮想の関数に引き渡されます。

どこに書くかいつも悩んでいたのでこういう慣習を知れたのはよかった

tag付ビルド

これらのソースコードは -tags cat フラグを付けてビルドすると cat.go が有効になり、dog.go は無効になります。

$ go build -tags cat

Functional Options Pattern

Server struct に公開フィールドとして Timeout や Logger を足したり、SetTimeout や SetLogger メソッドを追加したりすることはできます。しかし、利用者にタイムアウト値やロガーを任意のタイミングで変更させたくはありません。できれば New で指定した初期値から変更させないように制限したいですね。

Golang Functional Options Pattern | Golang Cafe

DDDとの親和性高そう

Method Value

少し混乱するかもしれませんが、Goのメソッド呼び出しは、第一引数にレシーバに持った関数の呼び出しと同義であるといえます。

type I int

func (i I) Add(n int) I {
  return i + I(n)
}

func main() {
  n = 1
  fmt.Println(n.Add(2))
  fmt.Println(I.Add(n, 2))
}

I.Add(n, 2) なんて呼び出しができるということを初めて知った

goroutine

goroutine は「ファイル I/O やネットワーク通信でブロックしている間にもランタイムが選んだ CPUコアで別の処理を平行して行うことで単純な並列処理よりも効率的に処理を行う」を主題にしているランタイムの機能です

これを知っているとゴルーチンをむやみやたらに使うことも減りそう

channel by range

ch := make(chan []byte)

for _, b := range ch {
}

channel を for で使えるの初めて知った

テストのパッケージ

テストをする際には、パッケージ名の指定に2つの方法があります。

  • テスト対象と同じパッケージ名称でテストコードを書く
  • テスト対象とは別のパッケージ名称でテストコードを書く

前者はパッケージ名称が同じですので、private関数もテストすることができます。一方、後者はあえて別名を付けることにより、privateな変数や関数にアクセスできないテストを書くことがるため、このパッケージの使用者と同じ実装をサンプルコードとして明示することができます

t.Skip / t.Skipf

テストによっては特定のOSでは実施できないものもあります。そういった場合には t.Skip または t.Skipf を使うことができます。

t.Short

-short を付けて実行した場合には testing.Short() が true を返します。各テストで -short のときにはスキップしたい場合には、t.SkipNow() を呼び出します。

t.Parallel

テストを並列実行したい場合に使うらしい

もう一つの注意点は、Table Driven Tests を平行で実行する際にはループの中で test変数を束縛することです。

for _, test := range tests {
  test := test // ← ★
  t.Run(test.name, func(t *testing.T) {
    t.Parallel()
    got := Add(test.lhs, test.rhs)
    if got != test.want {
      t.Errorf("%v: want %v, but %v:", test.name, test.want, got)
    }
  }
}

★部分の処理を入れないと、タイミングによってはすべてのテストが tests に含まれる最後の項目を参照してしまいます。

これは、今後注意しなくてもよくなるかも?なぜなら、loopvar という機能が今後入る可能性があるから。そのあたりも、mattnさんが Forkwell の Youtubeで話されていた。

Go言語プログラミングエッセンス - Forkwell Library#27 - YouTube

Goの標準外のパッケージ

パッケージ 説明
golang.org/x/arch マシンアーキテクチャ
golang.org/x/crypto 暗号化
golang.org/x/exp 実験的
golang.org/x/image 画像
golang.org/x/mod Go モジュール
golang.org/x/net ネットワーク
golang.org/x/oauth2 OAuth2
golang.org/x/sync 非同期
golang.org/x/sys システムコール
golang.org/x/term 端末
golang.org/x/text テキスト
golang.org/x/time 時間
golang.org/x/tools ツール
golang.org/x/xerrors エラー

flag.Duration / flag.DurationVar

第四章で time.Duration を紹介しましたが、これを flag として受け取ることができます

flag.Text / flag.TextVar

encoding.TextMarshaler は MarshalTextを、encoding.TextUnmarshaler は UnmarshalText メソッドを持ったインターフェースです。

type TestMarshaler interface {
  MarshalText() (text []byte, err error)
}

type TextUnmarshaler interface {
  UnmarshalText(text []byte) error
}

これらインターフェースを実装した型を flag パッケージで扱うことができます。

独自の型をテキストでパースできるのはよさそう

echo.FormFieldBinder / echo.CustomFunc()

echo でフォームデータを型にバインドするときに便利に使えるメソッドがあるみたい。

Binding | Echo

フォーム以外にも、クエリ文字列なんかもあるみたいで便利そう。

また、プリミティブな型以外にもバインドできる echo.CustomFunc() っていうのがあってこれも便利そうだった。

この書籍では inputタグでPOSTされる時刻フォーマットをパースする目的で使われていた。

私もこの問題に直面したことがあって、その時は別の方法で対処したんだけど、今後はこちらを使ったほうが見やすいコードになりそうだなと思った。

テンプレート処理

echo でテンプレートを扱いには Render メソッドを持った実装を e.Renderer に設定する必要があります。

e.Renderer = &Template {
  templates: template.Must(template.New("").
    Funcs(template.FuncMap{
      "FormatDateTime": formatDateTime,
    }).ParseFS(templates, "templates/*")),
}

こうすることで、echoからは以下のようにindexテンプレートにvalueを渡し、FormatDateTime関数を呼び出せるようになります。

return c.Render(http.StatusBadRequest, "index", value)
{{FormatDateTime .}}

echo にテンプレート渡せるのも知らんかったし、テンプレートにカスタム関数を追加できるのも知らんかった。

モジュールのメジャーバージョンアップ

ただし、go get -u は、モジュールのメジャーバージョンアップは行いません。メジャーバージョンアップをするには以下を実行します

$ go mod edit -replace=モジュール名@v=モジュール名@v

ent/ent

Ent (ent/ent) は Facebook 社が開発しているORMです。

今度何かDB関連の個人プロジェクトを作成するときに使ってみようかな。

まとめ

Go言語プログラミングエッセンスの読書感想ブログを書きました。

信頼の mattnさんの書籍で、予想通り良書でした。

Go言語のチュートリアルを一通りやって、次に読む書籍としてよいかと思いますので皆さんも読んでみてはいかがでしょうか?

webview/webiew を使って Linux上でフルスクリーンで表示する

動機

シングルアプリケーションをkioskモードで動かしたい。

そうすることで、ラズパイとかでアプリ作って・・・みたいなことがやりやすそう。

flutter-elinux でもいいんだけど、dart 覚えるの大変そう。

Go でできるなら Go でやりたい。

webview/webview とかでそれっぽいことできないかなぁ~?

昔は webview/webview でも Fullscreen() メソッドがあったけどなぜか最新ではなくなってる。

最新でも Fullscreen() したい!!

そんな感じの動機。

WebKitGtk

webview/webview は linux の場合、 Windowの表示に Gtk3 を使っているみたい。

webview/webview.go at 899018ad0e5cc22a18cd734393ccae4d55e3b2b4 · webview/webview · GitHub

さっきも書いた通り、Webviewにはウィンドウを操作するメソッドは生えてない。あるのはウィンドウへのポインタを返す Window() メソッドがあるのみ。そのポインタを使って勝手に良しなにしてくださいな。ということのようだ。

gotk3/gotk3

Go で Gtk3 を操作するためのライブラリに gotk3/gotk3 というのがある。

GitHub - gotk3/gotk3: Go bindings for GTK3

こいつには Fullscreen() のメソッドがある。

gtk package - github.com/gotk3/gotk3/gtk - Go Packages

なので、このライブラリに webview から取得したポイントを使って Window を作ればフルスクリーンにできるはず。

gotk3/gtk/window.go at master · gotk3/gotk3 · GitHub

ここら辺を見ると、自前で gtk.Window を作れそうだったので、それをベースにコードを書いてみた。

それが以下のコード

コード

package main

import (
    "github.com/webview/webview"
    "github.com/gotk3/gotk3/gtk"
    "github.com/gotk3/gotk3/glib"
    "unsafe"
)

func fullScreen(handle unsafe.Pointer) {
    obj := glib.Take(handle)
    w := &gtk.Window {
        gtk.Bin {
            gtk.Container {
                gtk.Widget {
                    glib.InitiallyUnowned { obj },
                },
            },
        },
    }
    w.Fullscreen()
}

func main() {
    w := webview.New(false)
    defer w.Destroy()

    fullScreen(w.Window())

    w.SetHtml("Thanks for using webview!")

    w.Run()
}

weston を使った フルスクリーンのデモ

weston.ini の [autolaunch] の path に ビルドしたバイナリのフルパスを指定して weston を起動すると以下のように表示されるはず。

VirtualBox 上でのフルスクリーン表示

まとめ

webview/webview を使って Linux上でフルスクリーンで表示してみました。

How to set fullscreen/maximize with newer API (`WebView.SetFullScreen()` have been removed) · Issue #458 · webview/webview · GitHub

ここでも webview の Fullscreen 表示の議論がなされているけど、まだ Open ステータスなので、今のところは 自前で Fullscreen する方法でやっていくしかなさそう。

flutter-elinux を起動するなら [shell] に書くより [autolaunch] に書いたほうが早く起動する

weston を何も指定せずに起動すると、weston-desktop-shell が起動します。

そうじゃなくて、自前のアプリを動かしたい場合は weston.ini の shell セクション の client エントリに実行ファイルのパスを指定してあげると、それが起動します。

flutter-elinux のアプリを起動したい場合は引数を指定する必要があり、clientエントリには引数は指定できないため シェルスクリプトを別途書いて、そいつを clientエントリで呼び出すようにしてあげると起動させることができます。

[shell]
client=/home/bamchoh/run.sh

こんな感じ。

でも結構起動が遅くて、5~10秒かかったりする。

でもさっき気づいたのが、 shellセクションじゃなくて autolaunch セクションに記述するほうほう。 こっちに指定したら即座に起動してびっくりした。

[autolaunch]
path=/home/bamchoh/run.sh

こうすると、weston-desktop-shell が起動した直後に所定のアプリが即座に起動します。 一瞬デスクトップっぽいのが見えたりするけど、それが気にならないならこっちのほうが早く起動できる。

まとめ

weston-desktop-shell が起動するのが気にならない人は [autolaunch] セクションを使ってみよう!

weston.ini で画面サイズを指定する方法

weston.ini に設定しているのに画面サイズが変わらないぞ?

weston.ini に 以下のように設定しても画面サイズが変わらなかった。

[output]
name=X1
mode=1280x768

nameの設定がキモだったようです。

weston.ini(5) — Arch manual pages

manページを見ると、nameはちゃんと決められた値にしないといけないようです。

Setting HDMI Resolution in Weston - Display

このサイトに記載しているサポートしている解像度ファイル /sys/class/drm/card0-HDMI-A-1/modes は 今使っているディスプレイ事にあるようで私の場合だと /sys/class/drm/ のしたに card0-Virtual-1card0-Virtual-8 があった。

上記のページでは HDMI-A-1name に使っていることから、 Virtual-1 を設定すればよいみたいだったのでそのようにしてみる。

[output]
name=Virtual-1
mode=1280x768

この設定にするとちゃんと解像度が設定された。

まとめ

weston で解像度を設定するときは [output] セクションの mode プロパティに解像度を WIDTHxHEIGHT のフォーマットで記述し、 name には /sys/class/drm/ 以下にあるファイルから所定のフォーマットに沿った形で指定すること。