Windows でもゴミ箱

Linux ばかりに触っていたらマニアックになるので Windows を久々に。

せっかくゴミ箱について興味をもったので Windows でも色々調べてみる。
OS の仕様としてゴミ箱を持つ Windows ではコマンドとかはどうなっているのかな?

コマンドプロンプトでごみ箱へファイルを移動させるにはどうすればよいのでしょうか? -OKWave

XP では c:\recycler 以下のはずだけど…てかドライブ毎ユーザー毎に存在するんだが何時の話?
試しにやってみた、ごみ箱に残らないとか書いているけどつまりこうなっているだけだと思う。

cmd_move

アプリを紹介しているけど VC++ を持っているならの SHFileOperation を使って簡単に作(略
なんにせよ Windows でも Shell API からしかアクセスできないんだ、ふむふむ。

OS 非依存の仮想マシンであるはずの .NET Framework はどうなのかな?

ファイルをゴミ箱へ削除(C#)について – Insider.NET

やはり P/Invoke でアクセスしなさい!にしているようで。
そんな糞面倒なことをするより VC++ でラッピングした dll を作って(略
それにしても「がっかり」って人… .NET Framework が何なのか解っているのかな?

Python は…当然 gio モジュールなんか使えるはずもなく。
これも VC++ でラッピングした dll を作って(もういいよ

Windows Power Shell なら結構簡単に

Windows Script Programming: PowerShellでファイルやフォルダをごみ箱に捨てる。

全然簡単じゃネェ!デフォルトで手段を用意してくれている分マシなだけだ。

うわぁ…つまりシェルてかこの手のことをやろうとすると VC++ が必要になるわ。
ま、こんなことを書いたしせっかくなのでゴミ箱へ送る DLL の作り方を書いておこう。
Visual Studio の入っている Vista の HDD に繋ぎ替えて…メンドクサ。

VisualStudio を起動する、VC++ Exp でも同じはず。
trash という名前の新規 Win32 コンソールプロジェクトを作る。
ウイザードを進めてアプリケーションの種類を「DLL」にして完了。
そして Python から使う場合にはまずやらなきゃいけないこと。

stdcall

と呼び出し規約を __stdcall にする、意味は勝手に調べてね。
達人なら違うと言うだろうけど普通の人ならそれ以外はデフォルトのままでいい。
つまりデフォルトの UNICODE ビルドだが今はそのほうが自然だろう。

追記
忘れていた!Releace 版だけでいいんだが
コード生成のランタイムライブラリを「マルチスレッド」にして。

multi

「マルチスレッド DLL」のままだと VC9 ランタイムも配らなきゃいけなくなる。
7kb が 41kb に増えるけどこれで DLL 単体で配布できるようになる
VC++ が入っている環境だと普通に動くので忘れがちなんですよねココ。
追記おわり

stdafx.h の最後の行に下記を追記

#include <shellapi.h>

コード

#include "stdafx.h"

extern "C" __declspec(dllexport)
BOOL trash(LPCWSTR szFileName)
{
	SHFILEOPSTRUCT sfos;
	ZeroMemory(&sfos, sizeof(SHFILEOPSTRUCT));
	wchar_t szOpFile[1024];
	wcscpy_s(szOpFile, 1024, szFileName);
	szOpFile[wcslen(szOpFile) + 1] = L'?0';
	sfos.hwnd = NULL;
	sfos.wFunc = FO_DELETE;
	sfos.pFrom = szOpFile;
	sfos.fFlags = FOF_SILENT | FOF_ALLOWUNDO | FOF_NOCONFIRMATION;
	if (!SHFileOperation(&sfos))
	{
		return TRUE;
	}
	return FALSE;
}

trash.zip

DllMain を書いて Visual Studio 2008 でビルドしたら警告になった。
分離されているのか、targetver.h といい親切なのか余計なお世話なのか。

細かいことは→の「APIで学ぶWindows徹底理解」に書いています。
ちなみに私が買ったこの本はもう読み直ししすぎてボロボロです。

後はそのままビルドで DLL の完成。
Python.exe があるディレクトリに trash.dll をコピーします。

exp

ここまでくれば python からは毎度のように ctypes を利用して

import ctypes

dll = ctypes.windll.trash
if dll.trash("C:\\Python30\\gomi.txt"):
    print("ok")
else:
    print("miss")

こんな感じであっさりゴミ箱へ捨てられます。
2.* ではファイル名を UNICODE にするのをお忘れなく。
どうしても Python でやらなきゃいけない場合以外は VC++ で作ったほうが(略

g_file_trash ってのがあった

何を今頃気がついた。
Eye of GNOME って Delete キーで「ゴミ箱へ移動」ができるんだね。

sakura241

これは…早速ソースコードを拝ませてもらわなきゃ、ダウンロード。
このポイントだけ知りたいんだから GPL 関係は大丈夫だろう。
というか Y901x はオープンソースだし。

Eye of GNOME

とにかく g_file_trash なんていう関数を使えばいいと解った。
しかしスゲェ、ゴミ箱に入れられるかどうかチェックして g_file_delete と振り分けている。
標準アプリはやはりこういう部分をキチンと考えて作っているんだなと関心する。

コレってやはり Gnome てか Nautilus に依存するのかな?
でもそんなの関係ねぇ(古い…最近マジでテレビ見ないし
つーか Y901x は D&D 処理自体から Nautilus に依存だ、わっはっはシラネ!

それよりこれって Python バインディングではどう書くのだ?
海外を探してもなーんにも見つからないんだが。

思いつくかぎりのワードで探して gio なんてモジュールがあると解った。
後は dir(gio) で gio.File を見つける、どうやらコレっぽい。

gio

あぁやっと見つかった。
何かもの凄い無駄なことをしているような気がしなくもないが私はいつもこんなだ。
ということで実験で書いてみたコード。

#!/usr/bin/env python
#-*- coding:utf-8 -*-

import gtk
import gio
import urllib
import os.path

class TrashWin(gtk.Window):
    """
        ドロップされたファイルをゴミ箱に捨てる
    """
    def __init__(self):
        gtk.Window.__init__(self)
        # D&D の準備
        dnd_list = [("text/uri-list", 0, 0)]
        self.drag_dest_set( gtk.DEST_DEFAULT_MOTION |
                            gtk.DEST_DEFAULT_HIGHLIGHT |
                            gtk.DEST_DEFAULT_DROP,
                            dnd_list, gtk.gdk.ACTION_MOVE )
        self.connect("drag_data_received", self.on_drop)
        # いつもの処理
        self.connect("delete-event", gtk.main_quit)
        self.resize(320, 150)
        self.show_all()

    def on_drop(self, widget, context, x, y, selection_data, info, time):
        drops = selection_data.data.split("\n")
        for drop in drops:
            name = urllib.unquote(drop)[7:-1]
            if os.path.isfile(name):
                # ゴミ箱へ Go!
                obj = gio.File(name)
                obj.trash()

if __name__ == "__main__":
    w = TrashWin()
    gtk.main()

こんなにアッサリ作れてしまった…例外や失敗の処理無しならたったの二行。
trash メソッドの引数は不要みたい、成功すると True が帰ってくる。

つーことで早速 Y901x に入れて更新!
ボタンも真似させてもらった、set_default_response で OK ボタンが選択できるのね。

Eye of GNOME プラグインは面倒

Eye of GNOME でリネームプラグインを作る。
って書いたけど本当に作れるんかいな?

Gedit/Plugins – GNOME Live!
EyeOfGnome/Plugins – GNOME Live!

gEdit とは扱いが随分違うなぁ… HowTo も何もない、勘で作れと?

Eye of GNOME Reference Manual

これ Python にバインディングされているのだろうか?

EyeOfGnome – SunagaLab

以前貼った所をよく見ると window.get_image() メソッドが使えているからできるのだろう。
ただ、9.04 付属の 2.26.1 はこの機能が標準装備、進化って悲しいなぁ。
他の Python への変換は勘でなんとかなると思う、今更 C で作りたく無いよ。

とにかく実際に書いてみるのが一番早い、とりあえず Plugins ページ一番下にあったコードは動いた。
次は gEdit プラグインを作ったのと同じように MessageBox を出すのを作って試す。

っっって

何これ、Eye of GNOME ってプラグインから例外を出すと終了してくれない!
端末から eog で起動して終了しなかったら Ctrl+C で強制終了さなきゃやってられん。
GUI アプリでコレをやるとは思いもしなかった。

eogdebug

2 と出ているのは debug 用でどこまで動いたかを print しているだけだから気にしない。
最初それが解らなくて再起動やログアウトをやるという無駄なことをしてしまった。

こんなことをやっている間に気がついた。
Ubuntu 9.04 って Ctrl+Alt+BackSpace で X の再起動ができなくなっている!

Ubuntu 9.04 Beta ctrl+alt+backspaceを有効にする。

$ sudo apt-get install dontzap
$ sudo dontzap -d

しなきゃいけないのね、何故わざわざ無効にする必要があったのか理解できない。
おいおい、そんなことをしている間に今日が終わっちゃう。
はたしてこの計画は進むのだろうか?

覚書ページをなんとかしたい

Y901x 0.1.3 公開しました。

結局トグルボタンとラジオボタンの同期は自前で行った。
どうやったかはソースコードを見てください、我ながらナイスなシグナルの逃し方だ。
本当はラジオボタンも同じメニュー選択で解除にしたいけど一般的な動作じゃないし。

boko

ツールバーにしてみたら上のようにボッコリするのと右寄せができないので気に入らなかった。
ソースコードにコメントアウトして残しています、XML のトコも外してね。

まだやることはあるけど、とにかくこれでしばらくは落ち着けるかもしれない。

プレイヤーが作りたいというより PyGtk で何か作ったものを公開したい。
Python で GUI アプリが簡単に作れ環境もデフォルトで揃っているコトを知らない人が多すぎる。
ウチへの検索ワードにヤケに mono が多い理由はソレだと思うので知ってもらいたい。
という感じでやっているのであまり複雑にしたくないし。

つか、そろそろ覚書ページをなんとかしたいんだが今やっているコトを解説は難しいぞ。
GtkUIManager はどう考えたってプログラミング初心者には敷居が高いんだが…
恐ろしく楽ができるウイジェットだけど事前知識がかなり必要になる。

かと思えば Glade もとても初心者には勧められないようなシロモノになっちまった。
個別ウイジェットの解説から始めてゼロから作り直ししたほうが良さげな感じ。

それはそうと、いいかげんに違うアプリを作りたいものだ。
Y901x のリネーム機能を Eye of GNOME のプラグインにしようかと企んでいる。
需要はシラネ、本日はお知らせのみ。

ラジオメニューとトグルボタンの同期

Y901x 0.1.2 公開しました。
こんな Windows 用のノリでやっていても良いものかと思い始めた今日この頃。

っっって…指定倍率変更の計算が間違えているのに今気がついた!
せっかくまとめて listbox クラスにしたのにセパレータサイズを足していた。
あーあ明日もごまかすために更新だ、更新が特定期間に集中する原因はコレです。

ところで。

ラジオメニューとトグルボタンは UIManager から同期させられないっぽい。
と以前書いたけどツールバーならあっさりできるようで。

#!/usr/bin/env python
#-*- coding:utf-8 -*-

import gtk

ui_str = """<ui>
    <menubar name="MenuBar">
        <menu action="Rep">
            <menuitem action="non"/>
            <menuitem action="one"/>
            <menuitem action="all"/>
            <menuitem action="rdm"/>
        </menu>
    </menubar>
    <toolbar name="Toolbar">
        <toolitem action="one"/>
        <toolitem action="all"/>
        <toolitem action="rdm"/>
    </toolbar>
</ui>"""

class BtnBox(gtk.Window):
    def __init__(self):
        gtk.Window.__init__(self)
        #
        # GtkUIManager 作成
        self.uimanager = gtk.UIManager()
        #
        # GtkActionGroup 作成
        self.actiongroup = gtk.ActionGroup("sasakimamenu")
        #
        # GtkAccelGroup を得てウインドウに突っ込む
        # 何もグループを作らなくてもコレやらないと後のアクセラレータ指定が効かない
        accelgroup = self.uimanager.get_accel_group()
        self.add_accel_group(accelgroup)
        #
        # GtkActionEntry を作成して突っ込む
        # 得にグループにしないものはココでまとめて作成
        # name, stock_id, label, accelerator, tooltip, callback
        self.ac0 = [("Rep", None, "リピート(_R)")]
        self.actiongroup.add_actions(self.ac0)
        #
        # GtkRadioActionEntry の List を作成
        # name, stock_id, label, accelerator, tooltip, value
        self.ac1 = [("non", None, "無し", "0", "無し", 0),
                    ("one", None, "シングル", "1", "シングル", 1),
                    ("all", None, "オール", "2", "オール", 2),
                    ("rdm", None, "ランダム", "3", "ランダム", 3) ]
        # GtkRadioAction を突っ込む
        # entries, value=0, on_change=None, user_data=None
        self.actiongroup.add_radio_actions(self.ac1, 0, self.on_loop_change)
        #
        # GtkUIManager の更新
        self.uimanager.insert_action_group(self.actiongroup, 0)
        self.uimanager.add_ui_from_string(ui_str)
        self.uimanager.ensure_update()
        # menubar を抜き出す
        menubar = self.uimanager.get_widget("/MenuBar")
        # toolbar を抜き出す
        toolbar = self.uimanager.get_widget('/Toolbar')
        # pack
        vbox = gtk.VBox()
        vbox.pack_start(menubar, False)
        vbox.pack_start(toolbar, False,False)
        self.add(vbox)
        self.connect("delete-event", gtk.main_quit)
        self.resize(320, 150)
        self.show_all()
        self.val = 0

    def on_loop_change(self, action, current):
        # コレは無理だった
        num = action.get_current_value()
        if num == self.val:
            action.set_current_value(0)
        else:
            self.val = num

if __name__ == "__main__":
    w = BtnBox()
    gtk.main()

repeat

得に何をするわけでもなく action を共通にすれば同期してくれるわ。
だけどアクティブなボタンを再度押すとオフにするといった処理は無理か。
ボタンオブジェクトを抜いて clicked シグナルを処理すればよさげだけど。

コレ上手く使えないかな?いまここ。