これまでのあらすじ:
2016年3月、フェルト生地を手で裁断している際にレーザーカッターがあれば複雑なカットが容易にできるなあと思って、安価になってきたレーザーカッターを購入しようと思ったのがきっかけ。調べていくうちに、合板も切れたほうがいいと思うようになって、CNCルーター(CNCミリング)についても考えるようになった。
Arduinoは以前から使っており、CNCシールドがあると気付いて自作も可能と思うようになった。当初はShapeOkoやX-CARVEを参考にMakerSlide、OpenRail、V-Wheel、2GTタイミングベルトなどで5万円くらいで自作しようと思っていた。AliExpressでも部品が安く買えることが分かって、しばらくは部品探し。探せば探すほど安くて本格的な部品も見つかってくるので、そんなにケチらなくてもいいのではないかと徐々にスペックアップ。最終的には剛性や精度のことも考えてボールスクリューやリニアスライドを使うことになり、予想以上に重厚な3軸CNCマシンをつくることに(約7万円)。
構想から約5週間(制作約3週間)でルーターとレーザーともに使えるようになり、現在はgrbl1.1+Arduino CNCシールドV3.5+bCNCを使用中(Macで)。余っていたBluetoothモジュールをつけてワイヤレス化。bCNCのPendant機能でスマホやタブレット上のブラウザからもワイヤレス操作可能。
その他、電子工作・プログラミング、最近は機械学習などもやっています。基本、Macを使っていますが、機械学習ではUbuntuを使っています。


CNCマシン全般について:
国内レーザー加工機と中国製レーザー加工機の比較
中国製レーザーダイオードについて
CNCミリングマシンとCNCルーターマシンいろいろ
その他:
利用例や付加機能など:
CNCルーター関係:

*CNCマシンの制作記録は2016/04/10〜の投稿に書いてあります。

2018年6月11日月曜日

Ubuntu 18.04:Gnome Shell Extensions自作:クリックでnvidia-smiの結果を表示させる

前回サンプルを改造して簡単なGnome Shell Extensionを自作しましたが、今回はトップバーからのドロップダウンメニュー表示にチャレンジしてみました。

今回つくったもの:
GPUの状態をチェックするための「nvidia-smi」というターミナルコマンドの結果をドロップダウンメニュー上に表示させる(結果は以下)


これまでは、わざわざターミナルを起動して「nvidia-smi」コマンドを入力して表示させていましたが、トップバー上のアイコンをワンクリックすればこのように表示されます。さらに表示中は1秒ごとに内容を更新するようにします。

今回ドロップダウンメニューやポップアップメニューのプログラムについては、このチュートリアルを参考にしました。どうやらトップバーからのドロップダウン形式のメニューをつくるには、もう部品化されたクラスを使うといいようです。
追記:
クラスを使わないコードを新たに追加しました。
追々記:
さらなる改良版を追加しました(最後の方)。


まずは前回同様ターミナルで、

gnome-shell-extension-tool --create-extension

を入力して対話形式でタイトルなど決めながら雛形を用意します。
そして先程のポップアップメニューのサンプルをコピペすると、


こんな感じの4種類のメニューを含んだドロップダウンメニューができあがります。
このサンプルは一通りのメニューパターンを網羅しているので今後も使えそうです。

しかしながら今回は、これらのメニューは必要なくて単なるドロップダウン形式で「nvidia-smi」の結果をテキスト表示させたいだけなので、この表示ウィンドウだけ利用させてもらいます。主には、下から3番目の文字表示してある部分「PopupMenuItem」以外消してしまいます。
それからトップバーにも虫眼鏡アイコン、文字、▼の3種類表示していますが、できれば自作アイコンだけを表示させたいと思います。


自作アイコンの表示:
そのままアイコンのパスを書けばいいのかと思いましたが、ここにやり方が書いてありました。チュートリアルやサンプルが少ないためにあいかわらず探すのが大変です。
自作アイコンはメインスクリプトと同じディレクトリに入れておいた場合、

const St = imports.gi.St;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const Gio = imports.gi.Gio;

let gicon=Gio.icon_new_for_string(Me.path + "/my_icon.png");

const icon = new St.Icon({ gicon: gicon, style_class: 'system-status-icon'})

となるようです(結構面倒)。そのままだと歪んでしまうので、最後に「style_class: 'system-status-icon'」を付け加えておくとトップパーにフィットしてくれます。


「nvidia-smi」のコマンドを送るコード:
コマンドを送るコードについては前回も使った、

let smi = GLib.spawn_command_line_sync("nvidia-smi").toString();

になります。そのまま「nvidia-smi」というコマンドを()内に書けばいいだけです。このコマンドを送れば、その結果として変数smiに文字列が代入されます。得られた文字列から必要な値だけを正規表現で抜き出して新たに書き直してもいいのですが面倒なので、そのまま結果を表示させることにしました。
追記:
そのまま「nvidia-smi」の返り値を「smi」に代入すると、先頭に「true,」と最後に「,,0」という文字が挿入されてしまうので、以下のように変更しました。
let smi = GLib.spawn_command_line_sync("nvidia-smi")[1].toString();

返り値は配列になっており、配列[0]には応答が成功した場合の「true」が含まれるようです(ダメなら「false」)。同様に[2]以降の「,,0」も不要なことから、[1]が実際に必要な文字列となるため上記のように変更しました(以下のコードも変更しておきました)。


このように余計な返り値を取り除き、必要な部分だけ表示されるようになりました。細かい部分ですが、少しずつわかってきたという感じです。


文字列を表示させるコード:
「nvidia-smi」で得た文字列を表示するには、

this.menuitem = new PopupMenu.PopupMenuItem(smi, {style_class:'smi-label'});

今回はこの部分に代入してみました。()内の「smi」が文字列になります。同時にCSSのスタイルを反映させるために「style_class」も記入しています。「smi-label」というのは、外部ファイル「stylesheet.css」内に記入してあるクラスです。

この「stylesheet.css」には、フォントの指定とフォントサイズの指定が書いてあります(以下)。

.smi-label {
  font-family: monospace;
  font-size: 15px;
}

フォントを等幅フォントのmonospaceなどに指定しないと、「nvidia-smi」から得た文字列が崩れてしまうので要注意(以下、崩れてしまった場合)。


表形式で文字列が出力されるために等幅フォントを使用しなければいけないということです。
背景色などはシステムに使用されているものと同じものが反映されるので、あとはフォントサイズを調整するくらいです。


1秒ごとに表示内容を更新させる:
このままだとクリックした瞬間の結果表示となるので待っていても内容は更新されません。そのため内容を更新させるプログラムを追加します。
先程、以下のように、

this.menuitem = new PopupMenu.PopupMenuItem(smi, {style_class:'smi-label'});

「nvidia-smi」から得た文字列をPopupMenuのオブジェクト生成と同時に渡しましたが、このオブジェクトへ後からテキストの値だけを渡すにはどうすればいいのか?いろいろ探しましたが、なかなか見当たりません。
ようやくGnome Shellのソースが見つかったので、なんとか解決しました。PopupMenuItem.jsに関してはこのページの239行目(以下)。

var PopupMenuItem = new Lang.Class({
    Name: 'PopupMenuItem',
    Extends: PopupBaseMenuItem,

    _init(text, params) {
        this.parent(params);
        this.label = new St.Label({ text: text });
        this.actor.add_child(this.label);
        this.actor.label_actor = this.label
    }
});

ここから察すると、どうやら「PopupMenuItem.label.text」で文字列を渡せそうです。メニュークラスなので文字列はラベル扱いで、そのラベルの下にテキストがあるようです。
ということから、アップデート関数をつくって、

update: function() {
    let timeout = GLib.timeout_add_seconds(0, 1, Lang.bind(this, function () { 
        this.menuitem.label.text = GLib.spawn_command_line_sync("nvidia-smi")[1].toString();
        return true;
    }));
},

このようにすれば、一秒ごとに内容更新されるはずです。これで主要なところはほぼ完成。
以下が、コード全体。

const Lang = imports.lang;
const St = imports.gi.St;
const GLib = imports.gi.GLib;

const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;

const Me = imports.misc.extensionUtils.getCurrentExtension();
const Gio = imports.gi.Gio;
let gicon=Gio.icon_new_for_string(Me.path + "/gpu-icon.png");

let smiPanel;

const SmiPanel = new Lang.Class({
    Name: 'SmiPanel',
    Extends: PanelMenu.Button,

    _init: function() {
        this.parent(0.0, "smi panel", false);

        const icon = new St.Icon({ gicon: gicon,style_class: 'system-status-icon'})
        
        this.actor.add_actor(icon);       
        this._box = new St.BoxLayout();
        this.actor.add_actor(this._box);
  
        let smi = GLib.spawn_command_line_sync("nvidia-smi")[1].toString();
        this.menuitem = new PopupMenu.PopupMenuItem(smi,{style_class:'smi-label'});
        this.menu.addMenuItem(this.menuitem);
    },

    update: function() {
        let timeout = GLib.timeout_add_seconds(0, 1, Lang.bind(this, function () { 
            this.menuitem.label.text = GLib.spawn_command_line_sync("nvidia-smi")[1].toString();
            return true;
        }));
    },
 
    destroy: function() {
        this.parent();
    }
});

function init() {}

function enable() {
    smiPanel = new SmiPanel();
    smiPanel.update();
    Main.panel.addToStatusArea("nvidia_smi", smiPanel, 0, "right");
}

function disable() {
    if (smiPanel) {
        Main.panel._rightBox.remove_actor(smiPanel.container);
        Main.panel.menuManager.removeMenu(smiPanel.menu);
        smiPanel.destroy();
        smiPanel = null;
    }
}

BoxLayoutは使う必要があるかわかりませんが、一応入れておきました。
それにしても、相変わらず手がかりが少ないのでわかりづらい。それでも、なんとか目標にしていた表示方法に達成できました。毎回Alt+F2を押してr(リターン)でのExtension再起動もキー操作が面倒なのでスクリプト化してしまいました(xdotool使用)。
しばらくはGnome shellのソースと他のExtensionsを参考に調べていくしかないという感じです。
とりあえず、わからなくなったらこのReferenceを見るといいのかもしれません。
WindowやMenuの種類については、ここに画像とともに説明してあります。ここで見てみると、PopupMenuクラスではなく、PanelMenuクラスを使えばよかったのかもしれません。次回はこの辺の違いを検証していきたいと思います。

しかしながら、徐々にカスタマイズされてきたので使いやすくなってきました。


追記(シンプル版):
その後、もう少し単純なコードで書けないか試してみました。今回はサンプルの「Hello World」のようにクラスを使わずfunctionだけでできるかどうか?
サンプルにあるように、

init関数
enable関数
disable関数

それと、表示内容を毎秒更新するための、

update関数

これらの4つで構成します。
それから表示する文字列の体裁を整えるために「stylesheet.css」も利用します。
全体コード「extension.js」は以下。

const St = imports.gi.St;
const GLib = imports.gi.GLib;
const Main = imports.ui.main;
const Lang = imports.lang;
const Button = imports.ui.panelMenu.Button;

let button;

function update() {
    let timeout = GLib.timeout_add_seconds(0, 1, Lang.bind(this, function () {
        //メニューが開いているときだけコマンド送信ならびに文字列を更新する
        if(button.menu.isOpen){
            button.menu.box.label_actor.text = GLib.spawn_command_line_sync("nvidia-smi")[1].toString();
        }
        return true;
    }));
}

function init() {
    //ボタンオブジェクト生成
    button = new Button(0, 'button', false);
 
    //ラベルで文字列アイコンをつくり、ボタンへ追加
    let icon = new St.Label({style_class: 'icon-label', text:'GPU'});
    button.actor.add_child(icon);
 
    //「nvidia-smi」返り値の文字列用labelオブジェクト生成
    //外部スタイルシート「smi-label」を反映させておく
    let label = new St.Label({text:'', style_class:'smi-label'});

    //ボタンにlabelを追加
    button.menu.box.add_child(label);
    //labelをlabel_actorにしアップデートできるようにしておく
    button.menu.box.label_actor = label;
}

function enable() {
    update();
    Main.panel.addToStatusArea('Popup', button, 0, 'right');
}

function disable() {
    if(button){
        Main.panel._rightBox.remove_actor(button.container);
        Main.panel.menuManager.removeMenu(button.menu);
        button.destroy();
        button = null;
    } 
}

またスタイルシート「stylesheet.css」の内容は以下。

.icon-label {
    padding-top: 0.3em;
}

.smi-label {
    font-family: monospace;
    font-size: 15px;
    padding-left: 1em;
    padding-right: 1em;
}


今回は、PanelMenu.jsのButtonクラスを利用しました。「Menu」という文字列だけを表示させれば以下のようなシンプルなプルダウン形式のウィンドウです。


この「Menu」という文字列の部分に「nvidia-sim」の表示内容を入れるだけです。

ボタンオブジェクトを生成したあと、アイコンや表示する文字列をLabelオブジェクトとして追加していき、update関数で表示文字列だけを更新するというシンプルな内容ですが、またもや新たに「nvidia-smi」コマンドで受け取った文字列を渡す際に手間取りました。
特に、init関数の最後の部分です。前回PopupMenuItemの場合は、

PopupMenuItem.label.text = '表示させたい文字列';

で更新する文字列を渡すことができたのですが、今回の場合は同じようにやってもだめで、

button.menu.box.label_actor = label;

このようにlabel_actorを定義しておいてから、

button.menu.box.label_actor.text = '表示させたい文字列';

という感じで渡さないとだめでした。
こっちのほうがきちんとした書き方なのかもしれないけれども、Referenceやソースを見てもわかりにくい。手がかりとなったのは、前回も確認した(ソース)、

var PopupMenuItem = new Lang.Class({
    Name: 'PopupMenuItem',
    Extends: PopupBaseMenuItem,

    _init(text, params) {
        this.parent(params);
        this.label = new St.Label({ text: text });
        this.actor.add_child(this.label);
        this.actor.label_actor = this.label
    }
});

この最後に書いてある部分を参考に試してみたら文字列を渡すことができました。
まだまだ分からないことばかりで、以下気になった部分を列挙しておきます。


ボタンオブジェクトを生成の際のパラメータ:

button = new Button(0, 'button', false);

()内の最初の「0」はドロップダウンメニューの位置のようで、0:右寄り、1:左寄り、0.5:中央となるようです。ただし、画面からはみ出ないように上書き調整されるようです。

「1」にした場合は、クリックするトップバーアイコンを基準に左側に表示されます。

「0.5」にした場合は、トップバーアイコンを基準に中央に表示されます。

基本は「0」のまま(右寄り)でいいかと。

それから()内中央はおそらくオブジェクトの名称だと思うので適当な文字列を記入。()内右側の「false」は「dontCreateMenu」というパラメータであり、「True」にしてしまうとアイコンそのものも消えてしまうようで、通常は「false」のままでいいかと。


アップデート関数:
今回のアップデート関数は何を参考にしたか忘れてしまいましたが(ここを参照)、

function update() {
    let timeout = GLib.timeout_add_seconds(0, 1, Lang.bind(this, function () { 
        button.menu.box.label_actor.text = GLib.spawn_command_line_sync("nvidia-smi")[1].toString();
        return true;
    }));
}

この書き方の場合、最後に「return true」を入れないと更新されないようです。
また別の書き方だと、

const Mainloop = imports.mainloop;
let timeout;


function update() {
    button.menu.box.label_actor.text = GLib.spawn_command_line_sync("nvidia-smi")[1].toString();
    timeout = Mainloop.timeout_add_seconds(1, function(){
        update();
    });
}

mainloopライブラリをインポートしておき、ループ内でこのupdate()自体を再度呼び出して関数自体をぐるぐる回す感じ。しかし、disable()の中に以下を書いて破棄しなければだめなのかも。

Mainloop.source_remove(timeout);
timeout = null;

GLib.timeout_add_seconds()の場合も、ループ内に自身をループさせるようにすれば、「return true;」なしでも動きます。
このあたりの違いは、徐々に調べながらでしょうか。
後から気づきましたが、このままだとメニューを閉じた状態でも、常に毎秒「nvidia-smi」のコマンドを送り続けているので「button.menu.isOpen」でメニューが開いているときだけコマンドを送ったほうがよさそうです。ということから以下のように変更しました。

function update() {
    let timeout = GLib.timeout_add_seconds(0, 1, Lang.bind(this, function () {
        if(button.menu.isOpen){ 
            button.menu.box.label_actor.text = GLib.spawn_command_line_sync("nvidia-smi")[1].toString();
        }
        return true;
    }));
}



文字列をトップバーアイコンにする場合:
アイコンの代わりに文字列をトップバーに埋め込むには、Labelオブジェクトに文字列を渡してからボタンに追加していましたが、そのままだと、


このように上端寄りになってしまいます(「GPU」の部分)。
何かパネル用のスタイルがあるのかもしれませんが、わからないので外部CSSファイル(stylesheet.css)に、

.icon-label {
    padding-top: 0.3em;
}

を追加記入して反映させることにしました。
そうすると、


このように他と同じよう表示されました。

ということで、まだまだ未知の部分が多くちょっとしたことでも一苦労という感じです。



追々記(さらに改良):
とりあえず動くことを目標としていましたが、一旦動き出すと気になる部分がでてきます。前回、メニューが開いているときだけ「nvidia-smi」のコマンド送信(毎秒)をさせました。しかし、それでもenable()内に書いたupdate()は空回りしながらも動き続けているので、メニューが開いているときだけupdate()が動くように変更しました。
手順として:

・待機状態
・ボタンが押された
・メニューが開いた
・ループ開始
・メニューが閉じた
・ループ終了
・待機状態に戻る

という感じ。ボタンが押されるまで待機するには、

button.connect('button-press-event', update);

「Hello World」のサンプルにも使われているこれを利用。つまりボタンが押されたら、update()を起動させるというものです。これはinit()に書いておきます。
ただし、このconnect()もどこかでループ待機しているはずなので、その辺の仕組みまでは検証しないことに。
ここで、ボタンが押されたときのフラグを用意しようかと思いましたが、

button.menu.isOpen = true/false

があるので、これを利用します(この辺はソースを見ながら、何が使えるか判断しています)。
つまり「ボタンが押された=メニューが開いた」ということなので以下のようにupdate()を変更。

function update(){
    if(button.menu.isOpen){
        let timeout = GLib.timeout_add_seconds(0, 1, Lang.bind(this, function () {
            button.menu.box.label_actor.text = GLib.spawn_command_line_sync("nvidia-smi")[1].toString();
            update();
        }));
    }
}

update()内の無限ループの途中に、メニューが開いたらというフラグをつけておきます。
アイコンをクリックした後、別の場所(他のウィンドウなど)をクリックした際にメニューが閉じてしまうこともあるので、クリックしたかどうかで判定するよりも、メニューが開いているかどうかで判定させたほうがよさそうです。
よって、これまでenable()内にupdate()を配置していましたが、それを消しておきます。
全体コード:

const St = imports.gi.St;
const GLib = imports.gi.GLib;
const Main = imports.ui.main;
const Lang = imports.lang;
const Button = imports.ui.panelMenu.Button;

let button;

function update(){
    if(button.menu.isOpen){
        let timeout = GLib.timeout_add_seconds(0, 1, Lang.bind(this, function () {
            button.menu.box.label_actor.text = GLib.spawn_command_line_sync("nvidia-smi")[1].toString();
            update();
        }));
    }
}

function init() {
    button = new Button(0, 'button', false);

    //トップバーにアイコン画像を使用する場合は以下 
    //let icon =  new St.Icon({ icon_name: 'system-search-symbolic', style_class: 'system-status-icon'});
    //今回は文字列をアイコン代わりに使用(スタイルシート反映)
    let icon = new St.Label({style_class: 'icon-label', text:'GPU'});
    button.actor.add_child(icon);

    let smi = '';
    let label = new St.Label({text:smi, style_class:'smi-label'});
    button.menu.box.add_child(label);
    button.menu.box.label_actor = label;

    //ボタン待機:押されたらupdate()発動
    button.actor.connect('button-press-event', update);
}

function enable() {
    Main.panel.addToStatusArea('gpu', button, 0, 'right');
}

function disable() {
    if(button){
        Main.panel._rightBox.remove_actor(button.container);
        Main.panel.menuManager.removeMenu(button.menu);
        button.destroy();
        button = null;
    }
}

こんな感じでしょうか。
「nvidia-smi」コマンドの応答に対して例外処理するほうがいいと思いますが、今回は 省略。きりがないですが、徐々に改良されてきました。
相変わらず情報源が少ないので難儀しますが、いまのところ以下を参考にしています。

(1)GNOME Shell Javascript Source Reference
どのようなUIがあるのか画像つきで説明してあるのでわかりやすい。ここで使いたいUIを探します。

(2)GNOME gnome-shell/js/ui/
ここにソースがあるので、(1)で見つけたUIのソースを開いて使えそうな関数を調べています。

(3)GNOME applications in JavaScript
また、ここに一通りのリファレンスがあるので参考にしています。

(4)Sr Reference
Stに関してならここでしょうか。

(5)GNOME Creating an Applet
ここにボタンやメニュー以外にもファイル入出力やHTTPリクエストなどのサンプルがあります。

2018年6月10日日曜日

Ubuntu 18.04:Anaconda仮想環境「conda activate py36」に変更

これまでは、Anacondaの仮想環境を切り替えるには、

source activate py36

をターミナルで入力していましたが、いつのまにか(conda4.4から)

conda activate py36

に変わっていたらしく、設定を変えてみました。



pyenvも導入しているため「activate」がそもそも重複しており問題がありました。おそらくこれで問題解消できるのかもしれません。変更方法については、conda Change logに説明があります。

これまでは「.bashrc」に、

export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"

export PATH="$PYENV_ROOT/versions/anaconda3-5.1.0/bin/:$PATH"

を記入していました。1〜3行目はpyenv用、4行目がAnaconda用。
それを以下のように変更(4行目だけ)。

export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"
. $PYENV_ROOT/versions/anaconda3-5.1.0/etc/profile.d/conda.sh

これで次回から、

conda activate py36

を入力して仮想環境を切り替えることになります。


ランチャー用のスクリプトの場合:
ランチャー(.desktopファイル)を使って「py36」仮想環境に入った状態でターミナルを起動させるには、

#!/bin/bash
eval '$BASH_POST_RC'
BASH_POST_RC='conda activate py36' gnome-terminal

このスクリプトを「py36.sh」などと保存して、

chmod +x py36.sh

で実行権限を与えておきます。
そして「.bashrc」のほうに

eval '$BASH_POST_RC'

を書いておいて、ターミナルが起動する前に「conda activate py36」が実行されるようにしておきます。
そしてランチャーのコマンドにこのスクリプトを実行させるため、

Exec=/home/mirrornerror/myScript/py36.sh

などと「py36.sh」までのパスも含めて記入しておきます。
あとはランチャーをダブルクリックすれば起動するはずです。


Macの場合:
MacのほうでもpyenvとAnacondaを導入しているので同じようにやってみましたが、Macの場合は「.bashrc」ではなく「.bash_profile」のほうに書くと問題なく起動しました。

export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"
. $PYENV_ROOT/versions/anaconda3-5.1.0/etc/profile.d/conda.sh

ちなみに、MacでのbCNC(Gコード送信アプリ)を開くためのランチャー用スクリプトは、

#!/bin/bash
. ~/.pyenv/versions/anaconda3-5.1.0/etc/profile.d/conda.sh
conda activate py27
python bCNC.py

にしています。


2018年6月5日火曜日

Ubuntu 18.04:ディスプレイ色温度設定

MacBookよりもディスプレイの色が青白い(眩しい)ような気がしたので色温度を変えられないかということで探してみると、「設定>デバイス>カラー」にありました。


色温度は「ディスプレイ」の項目にあるのかと思ったら「カラー>ラップトップの画面」をクリックし、「プロファイルの追加」をクリックすると別窓がでてきて、その中から選べるようです。
今回は「D55」を選び、やや暖色系にしてみました。黄色すぎると思いましたが、しばらく使っていると普通の色に見えてきます。逆にデフォルト(D65相当)に戻すと、青すぎて驚きます。


最初は「ソフトウェア」から「redshift」という機能拡張を試してみたのですが、調子が悪くてあまり使えませんでした。


「redshift」はgnome shell extensionsのほうにあり、そちらも試してみましたが、いまいち。


きちんと機能すれば便利そうなのですが、設定しても画面がちらついたり不安定なのでアンインストールしてしまいました。
いすれにせよ、何もインストールせずに先程の方法で色調整できたのでよかったです。
まだまだ、Ubuntu 18.04の細かなカスタマイズや調整は続きそうです。

2018年6月3日日曜日

Ubuntu 18.04:テキストエディター(gedit)で毎回新規ウィンドウで開く設定

ランチャーなどの簡単なスクリプトはテキストエディター(gedit)を使っていますが、複数書類を開くときにタブで表示されるので、新規ウィンドウで開くように設定できないかと。
調べてみると、ここにその方法が書いてありました。

「/usr/share/applications/org.gnome.gedit.desktop」のデスクトップファイルの中に記述されているコマンドを

Exec=gedit -s %U

にする。「-s」は新規ウィンドウで開くオプションのようで、「%U」についてはここに書いてあり、複数のファイルパスに対応しているようです。

それから、このコマンドでタブ表示不可'never'にします(dconfエディターでも可)。

gsettings set org.gnome.gedit.preferences.ui show-tabs-mode 'never'


dcofエディターの場合


このように設定し直したのですが、まだ少し不完全な挙動。
再度「/usr/share/applications」の中にある「.desktop」ファイルを見てみると、まず「org.gnome.gedit.desktop~」というのがあり、このファイルを書き換えたのですが、よく見ると末尾に「~」がついています。おそらくこれはバックアップファイル。


さらによく見てみると、「Text Editor」というファイルが2個もあります(以下)。



もともと表示名は日本語の「テキストエディター」でありファイル名も違うしわかりにくい。
いずれにせよ3つすべて書き換えればいいはずですが、この「Text Editor」というファイル名は「ls」コマンドでは出てこない。
「/usr/share/appliactions」の中を「ls -a | grep 'edit'」で探してみると、

gedit.desktop
nm-connection-editor.desktop
org.gnome.gedit.desktop
org.gnome.gedit.desktop~

この4つがでてくるので、2番目以外の3つを書き換えるということになります。
まず上から、

sudo nano gedit.desktop

で見てみると、ひとつだけ書き換えられていないものを発見。おそらくこれが理由で変な挙動になっていたのでは。残り2個は以前書き換えたときに、

Exec=gedit -s %U

にしたので大丈夫です。
ということで3つとも「-s」を付け加えて書き換えてみるときちんと別窓で開くようになりました。
それにしてもファイル名が表示名と違うのでわかりにくい。
Ubuntu 18.04にアップグレードしても、使っているうちにまだまだ細かな設定が必要になりそうです。

2018年5月31日木曜日

Ubuntu 18.04: Gnome-Shell-Extensionsでターミナル起動+Anaconda仮想環境に入る

Ubuntu 18.04のカスタマイズのついでに、自作Gnome Shell Extensionsにチャレンジ。ネットを探してもあまりサンプル例がなく、最初はこちらを参考にしました。


今回つくろうとしたもの:

・トップバーにアイコン(下画像:左から3個目のアイコン)をつける
・アイコンをクリックすると、Anacondaの仮想環境に入った状態でターミナル起動

というものです。


Anacondaの仮想環境を使っているため、通常はターミナルを立ち上げて、

source activate py36

を打ち込んで仮想環境に入りますが、この部分をワンクリックでできないかというものです。設定した「py36」という仮想環境には、機械学習用にTensorfowやPytorchなどのライブラリが入っています。


「source activate py36」のシェルスクリプト:
まず、仮想環境に入るためのコマンドである「source activate py36」を記入する「py36.sh」ファイルをつくります。そのまま「source activate py36」を実行させればいいというわけではなく(やってみたら失敗)、こちらを参考にしました。「source 〜」の場合は以下のようにやるといいようです。
追記:その後「conda activate py36」へ変更しました(こちらへ)。

・「py36.sh」ファイルをつくる(場所は問わず)。

・「py36.sh」の中に、以下の2行を書いて保存。

eval '$BASH_POST_RC'
BASH_POST_RC='source activate py36' gnome-terminal

・「py36.sh」を実行可能にするために、ファイルを右クリックで「プロパティ>アクセス権>プログラムとして実行可能」にチェック(「chmod +x py36.sh」でも可)。



・「.bashrc」ファイル末尾のほうに

eval "$BASH_POST_RC"

を書き込んでおく。以上。

・「py36.th」ファイルをダブルクリックすれば先程の画像のように仮想環境に入った状態で起動するはず。そのままランチャーにしてもいいし、desktopファイルと連携してもいいと思います。

そのままコマンドを書いただけだと実行したらターミナルは終了となってしまうので、今回のようにターミナルが起動する直前に「eval」を使って「.bashrc」からコマンドを実行させると大丈夫なようです。
あとは、このシェルスクリプトを実行させるGnome Shell Extensionsをつくります。

screenを使った場合(おまけ:その1):
「eval」の方法が分からず「screen」で仮想環境をつくって試したこともありました。
以下のスクリプトをランチャーのようにダブルクリックで起動させると、screenで仮想環境のなかのAnacondaの仮想環境の中に入った状態でターミナルが起動します。

#!/bin/bash

#まず外部からscreen仮想環境をつくる
screen -dmS sc1

#外部から、そのscreen仮想環境にAnaconda仮想環境をつくる
screen -S sc1 -X stuff 'source ~/.pyenv/versions/anaconda3-5.1.0/bin/activate py36'`echo -ne '\015'`

#最後にその二重の仮想環境に入る
gnome-terminal -e 'screen -x'

一応大丈夫なのですが、消し忘れるとすっど動いているということと、ここまで無理する必要もないかと、ようやく今回の方法に至りました。screenの勉強にはなったのでいずれ何かに使えればと。
尚、「sudo apt-get install screen」でインストール。

xdotoolを使った場合(おまけ:その2):
キー入力を自動化してくれる「xdotool」というのがあり、そのまま今回の「source activate py36(リターン)」を打ち込ませるスクリプトを書くだけ。

#!/bin/bash

#まずターミナル起動
gnome-terminal

#ターミナルが開くまで1秒ディレイ
sleep 1s

#仮想環境に入るコマンド
xdotool type 'source activate py36'

#最後にリターンキー入力
xdotool key Return

「py36.sh」などと保存し、右クリックで「プロパティ>アクセス権>プログラムとして実行可能」にチェック。ダブルクリックすればターミナルを起動しコマンドを実行してくれます。
長いスクリプトは「type」、一文字打つだけなら「key」を使って、普段ターミナルで打ち込むコマンドを書けばいいだけです。
この他、画面にフォーカスを与えたりマウス入力も可能なので、いろんなものを簡単に自動化できます。
xdotoolは、「sudo apt-get install xdotool」でインストール。


Gnome Shell Extensions(サンプル)をつくる:
Javascriptなので簡単なのかと思ったら独特の構文でわかりにくく、ネットで検索してもチュートリアルやサンプルも少なかったり(古かったり)するので、フルスクラッチでコーディングするよりも似たようなものを書き換えたほうがいいらしいです。
まずはこちらを参考に、ターミナルで以下を入力すると

gnome-shell-extension-tool --create-extension

対話形式で名前や説明など(とりあえず簡単に書いておく)を決めていくと、
「Hello World」サンプルをつくることができます。


Name: Open_py36
Description: Open Terminal @py36
Uuid: 記入しなかったため自動的に「Open_py__@mne-ubu」になった

そうすると、以下のような画面がでてきます。


これは、メインのスクリプトの「extension.js」。
あとは、CSSとJSONファイルが付属しています。


これらのデータは、「~/.local/share/gnome-shell/extensions/」にあります。


サンプルを動かしてみる:
サンプルを動かすには再起動が必要なのですが、「Alt+F2」を押すと画面はそのままでExtensionsの再起動ができます。「Alt+F2」を押すと以下の画面がでてくるのでここで「r」を入力してリターンを押します。


ちょっと画面が変化して再起動します。
「Gnome-Tweaks>機能拡張」を開くと、先程のExtensionがリストに加わっているのでオンにします(以下:上から二段目)。


そうすると、トップバー右上にアイコンがでてきます。
このサンプルはトップバー上のアイコンをクリックすると画面中央に「Hello World」という文字が出現してフェイドアウトしていく(2秒間)というものなので、「Gnome-Tweaks>外観>アニメーション」をオンにしておく必要があります。



スクリプトを書き換える:
次は、このサンプルをベースに最初につくった「py36.th」を実行させるプログラムに書き換えます。
スクリプトを実行させるファンクションは、検索するとここが参考になりました。

const Util = imports.misc.util;

Util.spawnCommandLine("script");

一行目でライブラリをインポートし、二行目はスクリプト実行すファンクションのようです。「script」のところに、「py36.th」のパスを入れればよさそうです。
「Hellow World」の表示内容を「env: py36」に変更し、位置も少し上の方に変えておきます。


const St = imports.gi.St;
const Main = imports.ui.main;
const Tweener = imports.ui.tweener;

//追加:ライブラリインポート
const Util = imports.misc.util;

let text, button;

function _hideHello() {
    Main.uiGroup.remove_actor(text);
    text = null;
}

function _showHello() {
    //以下表示テキスト内容を「env: py36」へ変更
    if (!text) {
        text = new St.Label({ style_class: 'helloworld-label', text: "env: py36" });
        Main.uiGroup.add_actor(text);
    }

    text.opacity = 255;

    let monitor = Main.layoutManager.primaryMonitor;

    //以下の高さ(monitor.height / 4)へ変更
    text.set_position(monitor.x + Math.floor(monitor.width / 2 - text.width / 2),
                      monitor.y + Math.floor(monitor.height / 4 - text.height / 2));

    Tweener.addTween(text,
                     { opacity: 0,
                       time: 2,
                       transition: 'easeOutQuad',
                       onComplete: _hideHello });

 //以下のスクリプト実行コマンドを追加
 Util.spawnCommandLine("/home/mirrornerror/Documents/myshell/py36.sh");
}

function init() {
    button = new St.Bin({ style_class: 'panel-button',
                          reactive: true,
                          can_focus: true,
                          x_fill: true,
                          y_fill: false,
                          track_hover: true });

    //以下のアイコンをターミナルへ変更
    let icon = new St.Icon({ icon_name: 'utilities-terminal-symbolic',
                             style_class: 'system-status-icon' });

    button.set_child(icon);
    button.connect('button-press-event', _showHello);
}

function enable() {
    Main.panel._rightBox.insert_child_at_index(button, 0);
}

function disable() {
    Main.panel._rightBox.remove_child(button);
}

5箇所ほど変更追加(赤文字)しただけです。
アイコンについては、「Icon Browser」が便利です。
以下のようにアプリケーションとして、既存のアイコンリストを見ることができます。
たしか「sudo apt-get gtk3-icon-browser」でインストールできたはずです。


NormalとSymbolicというのがあり、Symbolicがシンプルなアイコンです。


今回参考としたサイト:
あまりないのですが、以下。あとは既存のExtensionsのコードを参考に改造していくほうが早そうです。
GNOME Shellの拡張機能を作ってみよう
How I developed my first gnome-shell extension
GNOME WIKI: Extensions
github gnome-shell-extensions
GNOME Developer: Tutorial for beginners and code samples
GNOME Developer: gtk3-icon-browser


「Argos」というGnome-shell-extensions:
簡単なものであれば、gnome-shell-extensionsにある「Argos」というextensionが非常に便利です。トップバーに追加できる機能拡張で、よく使うフォルダや自作のスクリプトなども実行できます。


Argosのgithubサイトに使い方など詳しくのっています。

今回つくったAnaconda仮想環境に入るためのスクリプトも入れてみましたが結構簡単に実装できました。


この画像にあるように比較的よく使うものを組み込んでみました。
・普通のターミナル起動
・Anaconda仮想環境ターミナル起動
・ローカルネットワークサーバ起動
・各ディレクトリ(Home/Documents/Downloads)

一番下にある「argos.r.th」をクリックするとスクリプトを書くためのファイルが開きます(以下)。
#!/usr/bin/env bash

URL="github.com/p-e-w/argos"
DIR=$(dirname "$0")
HOME=/home/mirrornerror
DOC=/home/mirrornerror/Documents
DWN=/home/mirrornerror/Downloads
PYTHON=/home/mirrornerror/.pyenv/versions/anaconda3-5.1.0/bin//python

echo "| iconName=view-more-symbolic"
echo "---"
echo "Terminal | iconName=utilities-terminal-symbolic bash=pwd terminal=true"
echo "Anaconda py36 | iconName=utilities-terminal-symbolic bash='/home/mirrornerror/Documents/myshell/py36.sh' && 'terminal=false'"
echo "192.168.3.7:8008 | iconName=utilities-terminal-symbolic bash='$PYTHON -m http.server 8008 --bind 192.168.3.7' && 'terminal=true'"
echo "---"
echo "home | iconName=folder-symbolic href='file://$HOME'"
echo "Documents | iconName=folder-symbolic href='file://$DOC'"
echo "Downloads | iconName=folder-symbolic href='file://$DWN'"
echo "---"
echo "more"
echo "--$URL | iconName=help-faq-symbolic href='https://$URL'"
echo "--$DIR | iconName=folder-symbolic href='file://$DIR'"

いまのところこんな感じです。
echoを追加していくことで、内容がリスト化(ネストも可)されていきます。あとはタイトルとアイコン、そしてコマンドを記入していきます。外部にシェルスクリプトを配置しておけば、ここからコマンド起動させることができるので、いろんなことができそうです。

関連:
gnome shell extensions:自作その2

人気の投稿