ampv 1.0.3 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- OTU4NTMxOTlmYWI1Mzc3ODkxYTIwNDc2YmViYzhjZGYxNzY4MWUzZg==
4
+ OTI1MjM3YjlmNDJhZTdlMTdjOTY4MWU5YjMwZTdkZDZmYmJlNjMwYg==
5
5
  data.tar.gz: !binary |-
6
- NTgzOTFhM2Q3MzZjNmE5YjdlMTVhMDliYzlmYjIyODEwZDNhODUzOA==
6
+ ZGFjZTg2ZGRiZDI1NDY0MTAwNmRjY2Y1M2MzODY5YmIxZTJkYmY4Yg==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- MDJmODI2YmZiMWMzNmFiMmVkY2Y1N2U1OTQwNjQxMWViNTc2YmE1NTM3ZmIy
10
- MjQyYWQ1MWNhNDNmNGQzNmZkMzBhOTJmNzhhNDZlZWFjMzNkMjQ4ZDlkNjIz
11
- YTI1MjllMmFiMzIwMWIzNDA3MWQ4Y2VhODk1ODZkOGVkODQwYzY=
9
+ ZTA5ZjMxMTEzNjVhMWY2NGFkZWUwNjJiZmQ4ZjBkZDVjOTg5ZTdjZGMwZDBl
10
+ MWZmZmVlYTIzY2M0ZGM4ZjhiZGU2NTdkZTE2ZWYxNzc3ZDQyMDdmOGFjNzQ1
11
+ ODU5ZTJjYjMwMTYyMTBmNmQ2Yzk2ZWJkMzJiNTk5ZDBhZmI5Yjk=
12
12
  data.tar.gz: !binary |-
13
- YzU4MDcwMjg1MDg4N2UyMjQ0YjU4ODI5ODA4YTJjZjkzZTY5ZDk1ZGZlZjdm
14
- MzkwMzlkZTRiYzM1ZjgyMTg4MmVkZmY5NDk3ZTJmNzM2ZmQxYmVhYzE2ZGM0
15
- MGU1ZGM5NjQyOTQ1NjJjYjFmZDY1M2ZjYTBjODVkMmQ4OWEyZWM=
13
+ YWIzYWM1YWI2ZDU2OGQ2NGZiYjE3MzUyNzQ1YmQwZDYzZjQ5MzE4MjQ4Njcx
14
+ MzA2MmQyMGM3NDUyMmM3YzdlY2E0OWYyNzYwOTQ3YzhkODU1YTVjZDg3ZjNl
15
+ YTk4MzMxNjA2YzI3N2IzMWZiMTBkNjgyODg0NjU4YmI4NzZiMmI=
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2013 ahoka
3
+ Copyright (c) 2013-2014 ahoka
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
  this software and associated documentation files (the "Software"), to deal in
data/bin/ampv CHANGED
@@ -1,5 +1,9 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ unless $:.include?(lib = File.expand_path("../../lib", __FILE__))
4
+ $:.unshift(lib)
5
+ end
6
+
3
7
  require "ampv"
4
8
 
5
9
  Ampv::MainWindow.new
data/input.conf CHANGED
@@ -6,23 +6,24 @@ MOUSE_BTN7 playlist_prev
6
6
  MOUSE_BTN8 playlist_next
7
7
  MOUSE_BTN9 cycle pause
8
8
 
9
- RIGHT seek 10
10
- LEFT seek -10
11
- UP add volume 1
12
- DOWN add volume -1
9
+ RIGHT seek 10
10
+ LEFT seek -10
11
+ UP add volume 1
12
+ DOWN add volume -1
13
13
 
14
- PGUP seek -90 - exact
15
- PGDWN seek 90 - exact
16
- HOME add chapter -1
17
- END add chapter 1
18
- INS playlist_prev
19
- DEL playlist_next
14
+ PGUP seek -90 - exact
15
+ PGDWN seek 90 - exact
16
+ HOME add chapter -1
17
+ END add chapter 1
18
+ INS playlist_prev
19
+ DEL playlist_next
20
20
 
21
- f cycle fullscreen
22
- SPACE cycle pause
23
- ESC quit_watch_later
21
+ f cycle fullscreen
22
+ s cycle sub
23
+ SPACE cycle pause
24
+ ESC quit_watch_later
24
25
 
25
26
  # ampv commands
26
- l cycle playlist
27
- o open_file_chooser
28
- p cycle progress_bar
27
+ l cycle playlist
28
+ o open_file_chooser
29
+ p cycle progress_bar
@@ -1,31 +1,17 @@
1
-
2
1
  require "gtk2"
2
+ require "json"
3
+ require "uri"
4
+ require "ampv/config"
3
5
  require "ampv/mpvwidget"
4
6
  require "ampv/playlist"
5
7
  require "ampv/progressbarwidget"
6
8
  require "ampv/version"
7
- require "uri"
8
9
 
9
10
  module Ampv
10
11
  class MainWindow < Gtk::Window
11
12
 
12
- MAIN_CONF = "#{ENV["HOME"]}/.config/ampv.conf"
13
- INPUT_CONF = "#{ENV["HOME"]}/.mpv/input.conf"
14
- VIDEO_EXTS = [ ".avi", ".mkv", ".mp4", ".mpeg", ".mpg", ".ogm", ".ogv" ]
15
- KEY_NAMES = {
16
- "esc" => "Escape",
17
- "space" => "space",
18
- "right" => "Right",
19
- "left" => "Left",
20
- "up" => "Up",
21
- "down" => "Down",
22
- "pgup" => "Page_Up",
23
- "pgdwn" => "Page_Down",
24
- "home" => "Home",
25
- "end" => "End",
26
- "ins" => "Insert",
27
- "del" => "Delete",
28
- }
13
+ INPUT_CONF = "#{ENV["HOME"]}/.mpv/input.conf"
14
+ VIDEO_EXTS = [ ".avi", ".mkv", ".mp4", ".mpeg", ".mpg", ".ogm", ".ogv", ".rm", ".ts", ".wmv" ]
29
15
  WHEEL_BUTTONS = {
30
16
  Gdk::EventScroll::UP => 4,
31
17
  Gdk::EventScroll::DOWN => 5,
@@ -37,208 +23,128 @@ module Ampv
37
23
  BLANK_CURSOR = Gdk::Cursor.new(Gdk::Cursor::BLANK_CURSOR)
38
24
 
39
25
  def initialize
40
- load_config
26
+ unless defined?(MpvWidget::PATH)
27
+ dlg = Gtk::MessageDialog.new(nil,
28
+ Gtk::Dialog::DESTROY_WITH_PARENT,
29
+ Gtk::MessageDialog::ERROR,
30
+ Gtk::MessageDialog::BUTTONS_CLOSE,
31
+ "Unable to find mpv executable")
32
+ dlg.set_secondary_text("Please ensure you have mpv installed in your PATH.")
33
+ dlg.run
34
+ dlg.destroy
35
+ exit
36
+ end
37
+
38
+ args = ARGV.reject { |x| x[0] != "-" }
39
+ files = ARGV - args
40
+
41
+ print_version if args.include?("--version")
42
+ Config.load
41
43
  super
44
+
42
45
  set_title(PACKAGE)
43
- set_default_size(@config["width"], @config["height"])
46
+ set_default_size(Config["width"], Config["height"])
44
47
  set_window_position(Gtk::Window::POS_CENTER)
45
- move(@config["x"], @config["y"]) unless @config["x"] == -1 and @config["y"] == -1
48
+ move(Config["x"], Config["y"]) unless Config["x"] == -1 and Config["y"] == -1
46
49
 
50
+ add_events(Gdk::Event::POINTER_MOTION_MASK)
47
51
  Gtk::Drag.dest_set(self, Gtk::Drag::DEST_DEFAULT_ALL,
48
52
  [ [ "text/uri-list", 0, 0 ] ],
49
- Gdk::DragContext::ACTION_LINK)
50
- add_events(Gdk::Event::POINTER_MOTION_MASK)
53
+ Gdk::DragContext::ACTION_COPY)
51
54
 
52
55
  signal_connect("delete_event") { quit }
53
56
  signal_connect("scroll_event") { |w, e| handle_mouse_event(e) }
54
57
  signal_connect("button_press_event") { |w, e| handle_mouse_event(e) }
55
58
  signal_connect("key_press_event") { |w, e| handle_keyboard_event(e) }
56
59
  signal_connect("drag_data_received") { |w, dc, x, y, sd, type, time|
57
- handle_drop_event(sd.data, dc, time, false, true)
58
- }
59
- signal_connect("motion_notify_event") {
60
- window.set_cursor(LEFT_PTR)
61
- GLib::Source.remove(@cursor_timeout) if @cursor_timeout
62
- @cursor_timeout = GLib::Timeout.add(1000) {
63
- window.set_cursor(BLANK_CURSOR)
64
- } unless @mpv.is_paused
60
+ handle_drop_event(sd.uris, false, true)
61
+ Gtk::Drag.finish(dc, true, false, time)
65
62
  }
63
+ signal_connect("motion_notify_event") { mouse_cursor_timeout }
66
64
 
67
- vbox = Gtk::VBox.new
68
- add(vbox)
69
-
70
- args = process_args
71
- print_version if args.include?("--version")
72
- load_bindings
65
+ vbox = Gtk::VBox.new
66
+ @mpv = MpvWidget.new(args)
67
+ @progress_bar = ProgressBarWidget.new
68
+ @playlist = Playlist.new
69
+ @ignore_stop = false
73
70
 
74
- @mpv = MpvWidget.new(args, @config["scrobbler"])
75
71
  @mpv.signal_connect("file_changed") { |w, file|
76
- @playing = file
77
- @mpv.send("show_text ${media-title} 1500") if window.state.fullscreen?
72
+ @playing = URI.decode(file).sub(/^file:\/\/[^\/]*/, "")
73
+ # hacky work around when mpv catches drag and drop events.
74
+ unless @playlist.include?(@playing)
75
+ @playlist.clear(true)
76
+ if (file = create_playlist(@playing)) != @playing and File.directory?(@playing)
77
+ @playing = file
78
+ GLib::Idle.add { @mpv.load_file(@playing); false }
79
+ # mpv will fail to play the directory and then stop
80
+ # ignore the next stop event so the playlist does not advance
81
+ @ignore_stop = true
82
+ end
83
+ end
84
+ @mpv.send("show_text ${media-title} 1500") if window.state.fullscreen? and !@ignore_stop
78
85
  @playlist.set_selected(@playing)
79
86
  set_title(File.basename(@playing))
80
87
  }
81
- @mpv.signal_connect("length_changed") { |w, len|
82
- @length = len
83
- @playlist.update_length(@length)
84
- }
88
+ @mpv.signal_connect("playing_watched") { @playlist.on_playing_watched }
89
+ @mpv.signal_connect("length_changed") { |w, len| @playlist.update_length(@length = len) }
85
90
  @mpv.signal_connect("time_pos_changed") { |w, pos| @progress_bar.value = pos / @length.to_f }
86
91
  @mpv.signal_connect("stopped") {
87
92
  @progress_bar.value = 0
88
- next_file = @playlist.get_next
89
- @really_stop = next_file.nil? or @really_stop
90
93
  set_title(PACKAGE)
91
- if @really_stop and window.state.fullscreen?
92
- toggle_fullscreen
93
- elsif not @really_stop
94
- load_file(next_file, false)
94
+ unless @ignore_stop
95
+ next_file = @playlist.get_next
96
+ @really_stop ||= next_file.nil?
97
+ if !@really_stop
98
+ load_file(next_file, false)
99
+ elsif window.state.fullscreen?
100
+ toggle_fullscreen
101
+ end
95
102
  end
96
- @really_stop = false
103
+ @really_stop = @ignore_stop = false
97
104
  }
98
105
 
99
- vbox.pack_start(@mpv)
100
-
101
- @playlist = Playlist.new(@config["playlist_x"],
102
- @config["playlist_y"],
103
- @config["playlist_width"],
104
- @config["playlist_height"],
105
- @config["playlist_visible"])
106
-
107
- Gtk::Drag.dest_set(@playlist, Gtk::Drag::DEST_DEFAULT_ALL,
108
- [ [ "text/uri-list", 0, 0 ] ],
109
- Gdk::DragContext::ACTION_LINK)
110
-
111
106
  @playlist.signal_connect("open_file_chooser") { open_file_chooser }
112
107
  @playlist.signal_connect("drag_data_received") { |w, dc, x, y, sd, type, time|
113
- handle_drop_event(sd.data, dc, time, true, false)
108
+ handle_drop_event(sd.uris, true, false)
109
+ Gtk::Drag.finish(dc, true, false, time)
114
110
  }
115
111
  @playlist.signal_connect("play_entry") { |w, file| load_file(file, false, false, false, true) }
116
- @playlist.signal_connect("playing_removed") { @mpv.send("stop"); @really_stop = true }
112
+ @playlist.signal_connect("playing_removed") { @mpv.stop; @really_stop = true }
113
+
114
+ Gtk::Drag.dest_set(@playlist, Gtk::Drag::DEST_DEFAULT_ALL,
115
+ [ [ "text/uri-list", 0, 0 ] ],
116
+ Gdk::DragContext::ACTION_COPY)
117
117
 
118
- @progress_bar = ProgressBarWidget.new(@config["bar_color"],
119
- @config["head_color"],
120
- @config["progress_bar_height"])
121
118
  @progress_bar.add_events(Gdk::Event::BUTTON_PRESS_MASK)
122
119
  @progress_bar.signal_connect("button_press_event") { |w, e| handle_seek_event(e) }
123
- vbox.pack_start(@progress_bar, false)
124
120
 
121
+ vbox.pack_start(@mpv)
122
+ vbox.pack_start(@progress_bar, false)
123
+ add(vbox)
125
124
  show_all
126
- @mpv.start
127
-
128
- argv = ARGV.join(" ")
129
- if not argv.empty?
130
- load_file(argv)
131
- elsif not @config["playlist"].empty?
132
- @config["playlist"].each { |x| load_file(x, true, true, false) }
133
- @playlist.set_selected(@config["playlist_selected"])
134
- end
135
-
136
- Gtk.main
137
- end
138
-
139
- def load_config
140
- @config = {
141
- "width" => Gdk::Screen.default.width > 1280 ? 1280 : 853,
142
- "height" => Gdk::Screen.default.width > 1280 ? 726 : 486,
143
- "x" => -1,
144
- "y" => -1,
145
- "fullscreen_progressbar" => false,
146
- "progress_bar_visible" => true,
147
- "progress_bar_height" => 6,
148
- "bar_color" => "#8f5b5b",
149
- "head_color" => "#c48181",
150
- "playlist_width" => 360,
151
- "playlist_height" => 550,
152
- "playlist_x" => 0,
153
- "playlist_y" => 0,
154
- "playlist_visible" => true,
155
- "always_save_position" => false,
156
- "scrobbler" => "",
157
- "playlist_selected" => "",
158
- "playlist" => [ ],
159
- }
160
125
 
161
- if File.exists?(MAIN_CONF)
162
- File.readlines(MAIN_CONF).each { |line|
163
- key, _, val = line.partition("=")
164
- key = key.strip
165
- val = val.strip
166
- next unless @config.has_key?(key) and not key.start_with?("#")
167
-
168
- if @config[key].is_a?(Integer)
169
- val = val.to_i
170
- elsif @config[key].is_a?(TrueClass) or @config[key].is_a?(FalseClass)
171
- val = val == "true"
172
- elsif @config[key].is_a?(Array)
173
- val = val.split("|")
174
- elsif val.start_with?("#")
175
- begin
176
- c = Gdk::Color.parse(val)
177
- rescue
178
- puts("Invalid hexidecimal color for setting `#{key}': `#{val}")
179
- c = Gdk::Color.parse(@config[key])
180
- end
181
- val = c
182
- end
183
-
184
- @config[key] = val
185
- }
186
- end
187
- end
188
-
189
- def process_args
190
- args = [ ]
191
- ARGV.dup.each { |arg|
192
- if arg.start_with?("-")
193
- args.push(arg)
194
- ARGV.delete(arg)
126
+ if !files.empty?
127
+ if files.length > 1
128
+ files.each_with_index { |x, i| load_file(x, true, i != 0, false) }
129
+ else
130
+ load_file(files[0])
195
131
  end
196
- }
197
- return args
198
- end
199
-
200
- def load_bindings
201
- @mouse_bindings = [ ]
202
- @key_bindings = [ ]
203
- if File.exists?(INPUT_CONF)
204
- File.readlines(INPUT_CONF).each { |line|
205
- line = line.strip
206
- if line.start_with?("MOUSE_BTN")
207
- # 4 = up, 5 = down, 6 = left, 7 = right
208
- button, cmd = line.match(/MOUSE_BTN(\d+)(?:_DBL)?\s+(.+)$/).captures
209
- button = button.to_i + 1
210
- type = (4..7).include?(button) ? Gdk::Event::SCROLL :
211
- line.include?("DBL") ? Gdk::Event::BUTTON2_PRESS : Gdk::Event::BUTTON_PRESS
212
- @mouse_bindings[type] = [ ] if @mouse_bindings[type].nil?
213
- @mouse_bindings[type][button] = cmd
214
- elsif not line.empty?
215
- key, cmd = line.match(/^([^\s]+)\s+(.+)$/).captures
216
- if name = KEY_NAMES[key.downcase]
217
- keyval = Gdk::Keyval.from_name(name)
218
- else
219
- keyval = Gdk::Keyval.from_name(key)
220
- end
221
-
222
- @key_bindings[keyval] = cmd if keyval > 0
223
- end
224
- }
132
+ elsif Config["playlist"].length > 0
133
+ Config["playlist"].each { |x| @playlist.add_file(x["file"], x["length"], x["watched"]) }
134
+ @playlist.set_selected(Config["playlist_selected"])
225
135
  end
136
+
137
+ Gtk.main
226
138
  end
227
139
 
228
- def load_file(file, add_to_playlist=true, do_not_play=false, auto_add=true, force_play=false)
229
- return if file.nil? or file.empty?
140
+ private
141
+ def load_file(file, add_to_playlist = true, do_not_play = false, auto_add = true, force_play = false)
230
142
  file = File.expand_path(file) if file[0] == "~"
143
+ return unless (File.directory?(file) or valid_video_file?(file))
231
144
 
232
145
  if add_to_playlist
233
146
  if @playlist.count == 0 and auto_add and file !~ /^https?:\/\//
234
- dir = File.directory?(file) ? file : File.dirname(file)
235
- entries = Dir.entries(dir).sort
236
- entries.delete_if { |x| x.start_with?(".") or not valid_video_file(x) }
237
- entries.map { |x|
238
- x = dir + "/" + x
239
- @playlist.add_file(x)
240
- file = x if file == dir
241
- }
147
+ file = create_playlist(file)
242
148
  else
243
149
  @playlist.add_file(file)
244
150
  end
@@ -247,19 +153,23 @@ module Ampv
247
153
  @mpv.load_file(file, force_play) unless do_not_play
248
154
  end
249
155
 
156
+ def create_playlist(file)
157
+ dir = File.directory?(file) ? file : File.dirname(file)
158
+ entries = Dir.entries(dir).sort.map { |x| "#{dir}/#{x}" }
159
+ entries.delete_if { |x| x[0] == "." || !valid_video_file?(x) }
160
+ entries.each { |x| @playlist.add_file(x) }
161
+ file == dir ? entries[0] : file
162
+ end
163
+
250
164
  def handle_mouse_event(e)
165
+ mouse_cursor_timeout
251
166
  button = e.event_type == Gdk::Event::SCROLL ? WHEEL_BUTTONS[e.direction] : e.button
252
- return if @mouse_bindings[e.event_type].nil?
253
-
254
- process_cmd(@mouse_bindings[e.event_type][button])
255
-
256
- return true
167
+ return if Config["mouse_bindings"][e.event_type].nil?
168
+ process_cmd(Config["mouse_bindings"][e.event_type][button])
257
169
  end
258
170
 
259
171
  def handle_keyboard_event(e)
260
- process_cmd(@key_bindings[e.keyval])
261
-
262
- return true
172
+ process_cmd(Config["key_bindings"][e.keyval])
263
173
  end
264
174
 
265
175
  def handle_seek_event(e)
@@ -267,19 +177,19 @@ module Ampv
267
177
  pos = e.x / allocation.width * @length.to_f
268
178
  seek("seek #{pos} absolute")
269
179
  end
270
-
271
- return true
272
180
  end
273
181
 
274
- def handle_drop_event(data, context, time, do_not_play, replace)
275
- files = URI.decode(data).gsub("file://", "").split("\r\n")
182
+ def handle_drop_event(files, do_not_play, replace)
276
183
  @playlist.clear if replace
184
+ files.each { |x| load_file(URI.decode(x).sub(/^file:\/\/[^\/]*/, ""), true, do_not_play) }
185
+ end
277
186
 
278
- files.each { |x|
279
- load_file(x, true, do_not_play) if valid_video_file(x)
280
- }
281
-
282
- Gtk::Drag.finish(context, true, true, time)
187
+ def mouse_cursor_timeout
188
+ window.set_cursor(LEFT_PTR)
189
+ GLib::Source.remove(@cursor_timeout) if @cursor_timeout
190
+ @cursor_timeout = GLib::Timeout.add(1000) {
191
+ window.set_cursor(BLANK_CURSOR)
192
+ } unless @mpv.is_paused
283
193
  end
284
194
 
285
195
  def process_cmd(cmd)
@@ -288,6 +198,8 @@ module Ampv
288
198
  toggle_fullscreen
289
199
  when "cycle pause"
290
200
  @mpv.play_pause
201
+ when "stop"
202
+ @mpv.stop
291
203
  when "cycle playlist"
292
204
  @playlist.visible? ? @playlist.hide : @playlist.show
293
205
  when /seek /
@@ -309,11 +221,11 @@ module Ampv
309
221
  else
310
222
  @mpv.send(cmd) if cmd
311
223
  end
224
+ true
312
225
  end
313
226
 
314
227
  def seek(cmd)
315
- cmd = "no-osd " + cmd unless cmd.start_with?("no-osd") or
316
- not @progress_bar.visible?
228
+ cmd = "no-osd " + cmd unless cmd.start_with?("no-osd") or !@progress_bar.visible?
317
229
  @mpv.send(cmd)
318
230
  @mpv.send("get_property time-pos")
319
231
  end
@@ -323,9 +235,10 @@ module Ampv
323
235
  @progress_bar.show unless @progress_bar_user_hidden
324
236
  unfullscreen
325
237
  else
326
- @progress_bar.hide unless @config["fullscreen_progressbar"]
238
+ @progress_bar.hide unless Config["fullscreen_progressbar"]
327
239
  fullscreen
328
240
  end
241
+ mouse_cursor_timeout
329
242
  end
330
243
 
331
244
  def toggle_progress_bar
@@ -338,9 +251,8 @@ module Ampv
338
251
  end
339
252
  end
340
253
 
341
- def valid_video_file(x)
342
- return (VIDEO_EXTS.include?(File.extname(x)) or
343
- `file -b --mime-type "#{x}"`.start_with?("video"))
254
+ def valid_video_file?(x)
255
+ return (x and File.exists?(x) and !File.directory?(x) and VIDEO_EXTS.include?(File.extname(x).downcase))
344
256
  end
345
257
 
346
258
 
@@ -350,7 +262,6 @@ module Ampv
350
262
  [ Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL ],
351
263
  [ Gtk::Stock::OPEN, Gtk::Dialog::RESPONSE_ACCEPT ])
352
264
  dialog.select_multiple = true
353
- do_not_play = @playlist.count > 0
354
265
 
355
266
  filter = Gtk::FileFilter.new
356
267
  filter.name = "Video Files"
@@ -363,35 +274,35 @@ module Ampv
363
274
  dialog.add_filter(filterAll)
364
275
 
365
276
  dialog.filenames.each { |x|
277
+ do_not_play = @playlist.count > 0
366
278
  load_file(x, true, do_not_play)
367
- do_not_play = true
368
279
  } if dialog.run == Gtk::Dialog::RESPONSE_ACCEPT
369
280
  dialog.destroy
370
281
  end
371
282
 
372
283
  def print_version
373
- puts("#{PACKAGE} - v#{VERSION}\n" +
374
- "ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE} revision #{RUBY_REVISION}) [#{RUBY_PLATFORM}]\n\n" +
375
- `#{MpvWidget::PATH} --version`)
284
+ puts("#{PACKAGE} - v#{VERSION} (C) 2013-2014 ahoka\n" +
285
+ `ruby --version` +
286
+ `#{MpvWidget::PATH} --version`)
376
287
  exit
377
288
  end
378
289
 
379
- def quit(watch_later=false)
380
- @config["x"],
381
- @config["y"],
382
- @config["width"],
383
- @config["height"] = window.geometry unless window.state.fullscreen?
384
- @config["playlist_x"],
385
- @config["playlist_y"],
386
- @config["playlist_width"],
387
- @config["playlist_height"] = @playlist.window.geometry
388
- @config["playlist_visible"] = @playlist.visible?
389
- @config["playlist_selected"] = @playing
390
- @config["playlist"] = @playlist.get_entries.join("|")
391
- @config["progress_bar_visible"] = @progress_bar.visible?
392
- File.open(MAIN_CONF, "w") { |file| @config.each { |k, v| file.puts("#{k}=#{v}") } }
393
-
394
- @mpv.quit(@config["always_save_position"] ? true : watch_later)
290
+ def quit(watch_later = false)
291
+ Config["x"],
292
+ Config["y"],
293
+ Config["width"],
294
+ Config["height"] = window.geometry unless window.state.fullscreen?
295
+ Config["playlist_x"],
296
+ Config["playlist_y"],
297
+ Config["playlist_width"],
298
+ Config["playlist_height"] = @playlist.window.geometry
299
+ Config["playlist_visible"] = @playlist.visible?
300
+ Config["playlist_selected"] = @playing
301
+ Config["playlist"] = @playlist.get_entries.to_json
302
+ Config["progress_bar_visible"] = @progress_bar.visible?
303
+ Config.save
304
+
305
+ @mpv.quit(Config["always_save_position"] ? true : watch_later)
395
306
  Gtk.main_quit
396
307
  end
397
308
  end
@@ -0,0 +1,120 @@
1
+ module Ampv
2
+ class Config
3
+
4
+ @@config_file = "#{(ENV["XDG_CONFIG_HOME"] || "#{Dir.home}/.config")}/ampv.conf"
5
+ @@input_config = File.exists?("#{Dir.home}/.mpv/input.conf") ?
6
+ "#{Dir.home}/.mpv/input.conf" : File.expand_path("../../../input.conf", __FILE__)
7
+ @@key_names = {
8
+ "esc" => "Escape",
9
+ "space" => "space",
10
+ "right" => "Right",
11
+ "left" => "Left",
12
+ "up" => "Up",
13
+ "down" => "Down",
14
+ "pgup" => "Page_Up",
15
+ "pgdwn" => "Page_Down",
16
+ "home" => "Home",
17
+ "end" => "End",
18
+ "ins" => "Insert",
19
+ "del" => "Delete",
20
+ }
21
+ @@config = { }
22
+ @@defaults = {
23
+ "width" => Gdk::Screen.default.width > 1280 ? 1280 : 853,
24
+ "height" => Gdk::Screen.default.width > 1280 ? 726 : 486,
25
+ "x" => -1,
26
+ "y" => -1,
27
+ "fullscreen_progressbar" => false,
28
+ "progress_bar_visible" => true,
29
+ "progress_bar_height" => 6,
30
+ "bar_color" => Gdk::Color.parse("#8f5b5b"),
31
+ "head_color" => Gdk::Color.parse("#c48181"),
32
+ "playlist_width" => 360,
33
+ "playlist_height" => 550,
34
+ "playlist_x" => 0,
35
+ "playlist_y" => 0,
36
+ "playlist_visible" => true,
37
+ "always_save_position" => false,
38
+ "scrobbler" => "",
39
+ "playlist_selected" => "",
40
+ "playlist" => [ ],
41
+ "key_bindings" => [ ],
42
+ "mouse_bindings" => [ ]
43
+ }
44
+
45
+ def self.load
46
+ if File.exists?(@@config_file)
47
+ File.readlines(@@config_file).each { |line|
48
+ key, _, val = line.partition("=")
49
+ key.strip!
50
+ val.strip!
51
+ next unless @@defaults.has_key?(key) and key[0] != "#"
52
+
53
+ if @@defaults[key].is_a?(Integer)
54
+ val = val.to_i
55
+ elsif @@defaults[key].is_a?(TrueClass) or @@defaults[key].is_a?(FalseClass)
56
+ val = val == "true"
57
+ elsif @@defaults[key].is_a?(Gdk::Color)
58
+ begin
59
+ val = Gdk::Color.parse(val)
60
+ rescue
61
+ puts("Invalid hexidecimal color for setting `#{key}': `#{val}'")
62
+ next
63
+ end
64
+ elsif key == "playlist"
65
+ begin
66
+ val = JSON.parse(val)
67
+ rescue
68
+ puts("Failed to parse playlist JSON array.")
69
+ next
70
+ end
71
+ end
72
+
73
+ @@config[key] = val
74
+ }
75
+ end
76
+
77
+ # load input config
78
+ self["mouse_bindings"] = [ ]
79
+ self["key_bindings"] = [ ]
80
+ if File.exists?(@@input_config)
81
+ File.readlines(@@input_config).each { |line|
82
+ line.strip!
83
+ if line.start_with?("MOUSE_BTN")
84
+ # 4 = up, 5 = down, 6 = left, 7 = right
85
+ button, cmd = line.match(/MOUSE_BTN(\d+)(?:_DBL)?\s+(.+)$/).captures
86
+ button = button.to_i + 1
87
+ type = (4..7).include?(button) ? Gdk::Event::SCROLL :
88
+ line.include?("DBL") ? Gdk::Event::BUTTON2_PRESS : Gdk::Event::BUTTON_PRESS
89
+ self["mouse_bindings"][type] = [ ] if self["mouse_bindings"][type].nil?
90
+ self["mouse_bindings"][type][button] = cmd
91
+ elsif !line.empty?
92
+ key, cmd = line.match(/^([^\s]+)\s+(.+)$/).captures
93
+ if name = @@key_names[key.downcase]
94
+ keyval = Gdk::Keyval.from_name(name)
95
+ else
96
+ keyval = Gdk::Keyval.from_name(key)
97
+ end
98
+
99
+ self["key_bindings"][keyval] = cmd if keyval > 0
100
+ end
101
+ }
102
+ end
103
+ end
104
+
105
+ def self.[](key)
106
+ @@config[key].nil? ? @@defaults[key] : @@config[key]
107
+ end
108
+
109
+ def self.[]=(key, value)
110
+ @@config[key] = value
111
+ end
112
+
113
+ def self.save
114
+ File.open(@@config_file, "w") { |file|
115
+ @@config.each { |k, v| file.puts("#{k}=#{v}") unless k == "key_bindings" or k == "mouse_bindings" }
116
+ }
117
+ end
118
+ end
119
+ end
120
+
@@ -1,4 +1,5 @@
1
1
  require "fifo"
2
+ require "tmpdir"
2
3
 
3
4
  module Ampv
4
5
  class MpvWidget < Gtk::EventBox
@@ -6,112 +7,115 @@ module Ampv
6
7
  type_register
7
8
  signal_new("file_changed", GLib::Signal::RUN_FIRST, nil, nil, String)
8
9
  signal_new("length_changed", GLib::Signal::RUN_FIRST, nil, nil, Integer)
10
+ signal_new("playing_watched", GLib::Signal::RUN_FIRST, nil, nil)
9
11
  signal_new("time_pos_changed", GLib::Signal::RUN_FIRST, nil, nil, Float)
10
12
  signal_new("stopped", GLib::Signal::RUN_FIRST, nil, nil)
11
13
 
12
- PATH = "/usr/bin/mpv"
14
+ ENV["PATH"].split(":").each { |x|
15
+ if File.executable?("#{x}/mpv")
16
+ PATH = "#{x}/mpv"
17
+ break
18
+ end
19
+ }
13
20
 
14
21
  attr_reader :is_paused
15
22
 
16
- def initialize(args, scrobbler)
23
+ def initialize(args)
17
24
  if args.include?("--debug")
18
25
  args.delete("--debug")
19
26
  @debug = true
20
27
  end
21
28
 
22
- @scrobbler = scrobbler
23
- @mpv_path = "/usr/bin/mpv"
24
29
  @mpv_options = args.join(" ")
25
- @mpv_fifo = "/tmp/mpv.fifo." + Process.pid.to_s
30
+ @mpv_fifo = "#{Dir.tmpdir}/mpv.fifo." + Process.pid.to_s
31
+ @is_paused = true
26
32
 
27
33
  super()
34
+ signal_connect("realize") { start }
28
35
 
29
36
  @socket = Gtk::Socket.new
30
37
  @socket.modify_bg(Gtk::STATE_NORMAL, Gdk::Color.parse("#000"))
31
-
32
38
  @socket.signal_connect("plug_removed") { signal_emit("stopped"); true }
33
- add(@socket)
34
- end
35
39
 
36
- def start
37
- if @thread.nil?
38
- @fifo = Fifo.new(@mpv_fifo, :w, :nowait)
39
-
40
- cmd = "#{@mpv_path} \
41
- --identify \
42
- --idle \
43
- --input-file=#{@mpv_fifo} \
44
- --no-mouse-movements \
45
- --cursor-autohide=no \
46
- --msglevel=all=info \
47
- --wid=#{@socket.id} #{@mpv_options}"
48
- @thread = Thread.new { slave_reader(cmd) }
49
- end
40
+ add(@socket)
50
41
  end
51
42
 
52
43
  def send(cmd)
53
- @fifo.puts(cmd) unless @fifo.nil?
44
+ @fifo.puts(cmd) if @fifo
54
45
  end
55
46
 
56
- def load_file(file, force_play=false)
47
+ def load_file(file, force_play = false)
57
48
  send("loadfile \"#{file}\"")
58
49
  @force_play = force_play
59
50
  end
60
51
 
61
52
  def play_pause
62
53
  send("cycle pause")
63
- @is_paused = @is_paused ? false : true
54
+ @is_paused = !@is_paused
55
+ end
56
+
57
+ def stop
58
+ send("stop")
59
+ @is_paused = true
64
60
  end
65
61
 
66
62
  def quit(watch_later)
67
63
  send("quit" + (watch_later ? "_watch_later" : ""))
68
- @thread.join unless @thread.nil? or not @thread.alive?
64
+ @thread.kill if @thread
65
+ @prog_thread.kill if @prog_thread
69
66
  @fifo.close
70
- File.delete(@mpv_fifo)
71
67
  end
72
68
 
73
69
  private
74
- def slave_reader(cmd)
75
- @pipe = IO.popen(cmd, "a+")
76
-
77
- until @pipe.nil? or @pipe.closed? or @pipe.eof?
78
- line = @pipe.readline.chomp
79
- if line.include?("ID_FILENAME=")
80
- signal_emit("file_changed", (@playing = line.partition("ID_FILENAME=").last))
81
- send("get_property pause") # saved position also saves play state
82
-
83
- @prog_thread.kill unless @prog_thread.nil? or not @prog_thread.alive?
84
- @prog_thread = Thread.new { progress_update }
85
- elsif line.start_with?("ID_LENGTH=")
86
- signal_emit("length_changed", (@length = line.rpartition("=").last.to_i))
87
- elsif line.start_with?("ANS_pause=")
88
- @is_paused = line.rpartition("=").last == "yes"
89
- play_pause if @force_play and @is_paused
90
- elsif line.start_with?("ANS_time-pos=")
91
- signal_emit("time_pos_changed", line.rpartition("=").last.to_f)
92
- end
93
-
94
- if @debug or line.start_with?("Error")
95
- puts(line) unless line.start_with?("ANS_time-pos=") or
96
- line.start_with?("ANS_ERROR") or
97
- line.start_with?("Failed to get") or
98
- line.start_with?("Command ")
99
- end
100
- end
70
+ def start
71
+ return if @thread and @thread.alive?
72
+ @fifo = Fifo.new(@mpv_fifo, :w, :nowait)
73
+ ObjectSpace.define_finalizer(self, proc { File.delete(@mpv_fifo) })
74
+
75
+ cmd = "#{PATH} \
76
+ --idle \
77
+ --input-file=#{@mpv_fifo} \
78
+ --cursor-autohide=no \
79
+ --no-mouse-movements \
80
+ --msglevel=all=info \
81
+ --wid=#{@socket.id} #{@mpv_options}"
82
+
83
+ @thread = Thread.start {
84
+ IO.popen(cmd) { |io|
85
+ io.each { |line|
86
+ line.chomp!
87
+ if line.include?("Playing: ")
88
+ signal_emit("file_changed", (@playing = line.partition("Playing: ")[-1]))
89
+ send("get_property length")
90
+ send("get_property time-pos")
91
+ send("get_property pause")
92
+ elsif line.start_with?("ANS_length=")
93
+ signal_emit("length_changed", (@length = line.rpartition("=")[-1].to_i))
94
+ elsif line.start_with?("ANS_pause=")
95
+ @is_paused = line.rpartition("=")[-1] == "yes"
96
+ @prog_thread.kill if @prog_thread
97
+ @prog_thread = Thread.new { progress_update }
98
+ play_pause if @force_play and @is_paused
99
+ elsif line.start_with?("ANS_time-pos=")
100
+ signal_emit("time_pos_changed", line.rpartition("=")[-1].to_f)
101
+ end
102
+
103
+ puts(line) if @debug and !line.start_with?("ANS_")
104
+ }
105
+ }
106
+ }
101
107
  end
102
108
 
103
109
  def progress_update
104
110
  scrobbled = false
105
111
  watched = 0
106
-
107
112
  loop {
108
113
  send("get_property time-pos") unless @is_paused
109
-
110
- unless @scrobbler.nil? or scrobbled or watched < @length * 0.5
111
- system("#{@scrobbler} \"#{@playing}\"")
114
+ unless scrobbled or watched < @length * 0.5
115
+ system("#{Config["scrobbler"]} \"#{@playing}\"") if Config["scrobbler"]
116
+ signal_emit("playing_watched")
112
117
  scrobbled = true
113
118
  end
114
-
115
119
  sleep(1)
116
120
  watched += 1 unless @is_paused
117
121
  }
@@ -119,9 +123,10 @@ module Ampv
119
123
 
120
124
  def signal_do_file_changed(file) end
121
125
  def signal_do_length_changed(len) end
126
+ def signal_do_playing_watched; end
122
127
  def signal_do_time_pos_changed(pos) end
123
- def signal_do_stopped()
124
- @prog_thread.kill
128
+ def signal_do_stopped
129
+ @prog_thread.kill if @prog_thread
125
130
  end
126
131
  end
127
132
  end
@@ -1,5 +1,3 @@
1
- #require "gtk2"
2
-
3
1
  module Ampv
4
2
  class Playlist < Gtk::Window
5
3
 
@@ -8,7 +6,9 @@ module Ampv
8
6
  signal_new("playing_removed", GLib::Signal::RUN_FIRST, nil, nil)
9
7
  signal_new("open_file_chooser", GLib::Signal::RUN_FIRST, nil, nil)
10
8
 
11
- def initialize(x, y, w, h, is_visible)
9
+ WATCHED_PIXBUF = Gtk::Invisible.new.render_icon(Gtk::Stock::OK, Gtk::IconSize::MENU)
10
+
11
+ def initialize
12
12
  buttons = {
13
13
  [ Gtk::Stock::OPEN, "Add to Playlist" ] => lambda { signal_emit("open_file_chooser") },
14
14
  [ Gtk::Stock::GO_UP, "Move Up" ] => lambda { move_selected_up },
@@ -17,11 +17,11 @@ module Ampv
17
17
  [ Gtk::Stock::CLEAR, "Clear Playlist" ] => lambda { clear }
18
18
  }
19
19
 
20
- super()
20
+ super
21
21
  set_title("Playlist - #{PACKAGE}")
22
- set_default_size(w, h)
22
+ set_default_size(Config["playlist_width"], Config["playlist_height"])
23
23
  set_skip_taskbar_hint(true)
24
- move(x, y)
24
+ move(Config["playlist_x"], Config["playlist_y"])
25
25
 
26
26
  signal_connect("show") { move(@pos[0], @pos[1]) unless @pos.nil? }
27
27
  signal_connect("hide") { @pos = window.root_origin }
@@ -31,38 +31,36 @@ module Ampv
31
31
  hide_on_delete if e.keyval == Gdk::Keyval::GDK_Escape
32
32
  }
33
33
 
34
- vbox = Gtk::VBox.new(false, 10)
35
- vbox.border_width = 10
36
- add(vbox)
34
+ vbox = Gtk::VBox.new(false, 10)
35
+ sw = Gtk::ScrolledWindow.new
36
+ @model = Gtk::ListStore.new(String, String, Gdk::Pixbuf)
37
+ @treeview = Gtk::TreeView.new(@model)
38
+ hbox = Gtk::HBox.new(true, 5)
39
+ @menu = Gtk::Menu.new
37
40
 
38
- sw = Gtk::ScrolledWindow.new
41
+ vbox.border_width = 10
39
42
  sw.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC)
40
- vbox.pack_start(sw)
41
43
 
42
- @model = Gtk::ListStore.new(String, String)
43
- @treeview = Gtk::TreeView.new(@model)
44
44
  @treeview.enable_search = false
45
45
  @treeview.rubber_banding = true
46
46
  @treeview.reorderable = true
47
47
  @treeview.selection.mode = Gtk::SELECTION_MULTIPLE
48
+ @treeview.tooltip_column = 0
48
49
 
49
- @treeview.signal_connect("row_activated") { |_w, p, c|
50
+ @treeview.signal_connect("row_activated") { |w, p, c|
50
51
  signal_emit("play_entry", @model.get_iter(p)[0])
51
52
  }
52
- @treeview.signal_connect("key_press_event") { |_w, e|
53
+ @treeview.signal_connect("key_press_event") { |w, e|
53
54
  remove_selected if e.keyval == Gdk::Keyval::GDK_Delete
54
55
  }
55
- @treeview.signal_connect("button_press_event") { |_w, e|
56
- @menu.popup(nil, nil, e.button, e.time) if
57
- e.event_type == Gdk::Event::BUTTON_PRESS and e.button == 3
56
+ @treeview.signal_connect("button_press_event") { |w, e|
57
+ @menu.popup(nil, nil, e.button, e.time) if e.event_type == Gdk::Event::BUTTON_PRESS and e.button == 3
58
58
  }
59
59
 
60
- ["Name", "Length"].each_with_index { |_x, i|
60
+ ["Name", "Length"].each_with_index { |x, i|
61
61
  renderer = Gtk::CellRendererText.new
62
- column = Gtk::TreeViewColumn.new(_x,
63
- renderer,
64
- :text => i)
65
- if _x == "Name"
62
+ column = Gtk::TreeViewColumn.new(x, renderer, :text => i)
63
+ if x == "Name"
66
64
  renderer.ellipsize = Pango::ELLIPSIZE_MIDDLE
67
65
  column.expand = true
68
66
  column.set_cell_data_func(renderer) { |t, c, m, j|
@@ -72,105 +70,87 @@ module Ampv
72
70
 
73
71
  @treeview.append_column(column)
74
72
  }
75
-
76
- sw.add(@treeview)
77
-
78
- hbox = Gtk::HBox.new(true, 5)
73
+ @treeview.append_column(Gtk::TreeViewColumn.new("", Gtk::CellRendererPixbuf.new, :pixbuf => 2))
79
74
 
80
75
  buttons.each { |k, v|
81
76
  button = Gtk::Button.new
77
+ item = Gtk::ImageMenuItem.new(k[1])
78
+
82
79
  button.image = Gtk::Image.new(k[0], Gtk::IconSize::BUTTON)
83
80
  button.height_request = 36
84
81
  button.set_tooltip_text(k[1])
85
82
  button.signal_connect("clicked") { v.call }
86
- hbox.pack_start(button)
87
- }
88
-
89
- vbox.pack_start(hbox, false)
90
83
 
91
- @menu = Gtk::Menu.new
92
- buttons.each { |k, v|
93
- item = Gtk::ImageMenuItem.new(k[1])
94
84
  item.image = Gtk::Image.new(k[0], Gtk::IconSize::MENU)
95
85
  item.signal_connect("activate") { v.call }
86
+
87
+ hbox.pack_start(button)
96
88
  @menu.append(item)
97
89
  }
90
+
91
+ sw.add(@treeview)
92
+ vbox.pack_start(sw)
93
+ vbox.pack_start(hbox, false)
94
+ add(vbox)
98
95
  @menu.show_all
99
96
 
100
- show_all if is_visible
97
+ show_all if Config["playlist_visible"]
101
98
  end
102
99
 
103
100
  def count
104
101
  i = 0
105
102
  @model.each { i += 1 }
106
- return i
103
+ i
107
104
  end
108
105
 
109
- def add_file(file)
110
- contains = false
111
- @model.each { |m, p, iter|
112
- if iter[0] == file
113
- contains = true
114
- break
115
- end
116
- }
117
- unless contains
106
+ def add_file(file, length = nil, watched = false)
107
+ unless include?(file)
118
108
  iter = @model.append
119
109
  iter[0] = file
110
+ iter[1] = length if length
111
+ iter[2] = WATCHED_PIXBUF if watched
120
112
  end
121
113
  end
122
114
 
123
- def move_selected_up
124
- @treeview.selection.selected_rows.each { |path|
125
- tmp = path.dup
126
- break if not tmp.prev! or @treeview.selection.selected_rows.include?(tmp)
127
- @model.move_before(@model.get_iter(path), @model.get_iter(tmp))
128
- }
115
+ def on_playing_watched
116
+ @playing_iter[2] = WATCHED_PIXBUF
129
117
  end
130
118
 
131
- def move_selected_down
132
- @treeview.selection.selected_rows.reverse.each { |path|
133
- break if @treeview.selection.selected_rows.include?((tmp = path.dup.next!))
134
- tmp = @model.get_iter(tmp)
135
- @model.move_after(@model.get_iter(path), tmp) unless tmp.nil?
136
- }
137
- end
138
-
139
- def remove_selected
140
- to_remove = [ ]
141
- @treeview.selection.selected_rows.each { |path|
142
- to_remove.push(@model.get_iter(path))
143
- }
144
- to_remove.each { |iter|
145
- signal_emit("playing_removed") if iter[0] == @playing
146
- @model.remove(iter)
147
- }
119
+ def include?(file)
120
+ @model.each { |m, p, iter| return true if iter[0] == file }
121
+ false
148
122
  end
149
123
 
150
124
  def get_next
151
125
  @model.each { |m, p, iter|
152
- return iter.next! ? iter[0] : nil if iter[0] == @playing
126
+ return iter.next! ? iter[0] : nil if iter == @playing_iter
153
127
  }
154
- return nil
128
+ nil
155
129
  end
156
130
 
157
131
  def get_prev
158
132
  prev = nil
159
133
  @model.each { |m, p, iter|
160
- return prev if iter[0] == @playing
134
+ return prev if iter == @playing_iter
161
135
  prev = iter[0]
162
136
  }
163
137
  end
164
138
 
165
139
  def get_entries
166
140
  entries = [ ]
167
- @model.each { |m, p, iter| entries.push(iter[0]) }
168
- return entries
141
+ @model.each { |m, p, iter|
142
+ entries << {
143
+ "file" => iter[0],
144
+ "length" => iter[1] ? iter[1] : "",
145
+ "watched" => !iter[2].nil?
146
+ }
147
+ }
148
+ entries
169
149
  end
170
150
 
171
- def clear
151
+ def clear(quiet = false)
172
152
  @model.clear
173
- signal_emit("playing_removed")
153
+ signal_emit("playing_removed") unless quiet or @playing_iter.nil?
174
154
  end
175
155
 
176
156
  def set_selected(file)
@@ -179,6 +159,7 @@ module Ampv
179
159
  @model.each { |m, p, iter|
180
160
  if iter[0] == @playing
181
161
  @treeview.set_cursor(Gtk::TreePath.new(i), nil, false)
162
+ @playing_iter = iter
182
163
  break
183
164
  end
184
165
  i += 1
@@ -186,15 +167,40 @@ module Ampv
186
167
  end
187
168
 
188
169
  def update_length(length)
189
- @model.each { |m, p, iter|
190
- if iter[0] == @playing
191
- iter[1] = Time.at(length).utc.strftime("%H:%M:%S") unless length == 0
192
- break
193
- end
194
- }
170
+ return if length == 0
171
+ @playing_iter[1] = Time.at(length).utc.strftime("%H:%M:%S") if @playing_iter
195
172
  end
196
173
 
197
174
  private
175
+ def move_selected_up
176
+ @treeview.selection.selected_rows.each { |path|
177
+ tmp = path.dup
178
+ # prev!: Returns: true if path has a previous node, and the move was made.
179
+ break unless tmp.prev! or @treeview.selection.selected_rows.include?(tmp)
180
+ @model.move_before(@model.get_iter(path), @model.get_iter(tmp))
181
+ }
182
+ end
183
+
184
+ def move_selected_down
185
+ @treeview.selection.selected_rows.reverse.each { |path|
186
+ # next!: Moves the path to point to the next node at the current depth. Returns self
187
+ break if @treeview.selection.selected_rows.include?(tmp = path.dup.next!)
188
+ tmp = @model.get_iter(tmp)
189
+ @model.move_after(@model.get_iter(path), tmp) unless tmp.nil?
190
+ }
191
+ end
192
+
193
+ def remove_selected
194
+ to_remove = [ ]
195
+ @treeview.selection.selected_rows.each { |path|
196
+ to_remove << @model.get_iter(path)
197
+ }
198
+ to_remove.each { |iter|
199
+ signal_emit("playing_removed") if iter == @playing_iter
200
+ @model.remove(iter)
201
+ }
202
+ end
203
+
198
204
  def signal_do_play_entry(file) end
199
205
  def signal_do_playing_removed() end
200
206
  def signal_do_open_file_chooser() end
@@ -1,30 +1,27 @@
1
-
2
1
  module Ampv
3
2
  class ProgressBarWidget < Gtk::DrawingArea
4
- def initialize(bar_color, head_color, height)
5
- super()
3
+
4
+ def initialize
5
+ super
6
6
  modify_bg(Gtk::STATE_NORMAL, Gdk::Color.parse("#000"))
7
- set_size_request(-1, height)
7
+ set_size_request(-1, Config["progress_bar_height"])
8
8
 
9
- signal_connect("expose_event") {
10
- @cx = window.create_cairo_context
11
- draw_widget
12
- }
13
9
  @value = 0
14
- @bar_color = bar_color
15
- @head_color = head_color
16
- end
10
+ @bar_color = Config["bar_color"]
11
+ @head_color = Config["head_color"]
17
12
 
18
- def draw_widget
19
- @cx.set_source_color(@bar_color)
20
- @cx.rectangle(0, 0, (allocation.width * @value.to_f).round, allocation.height)
21
- @cx.fill
22
-
23
- if @value > 0
24
- @cx.set_source_color(@head_color)
25
- @cx.rectangle((allocation.width * @value.to_f).round, 0, 2, allocation.height)
13
+ signal_connect("expose_event") {
14
+ @cx = window.create_cairo_context
15
+ @cx.set_source_color(@bar_color)
16
+ @cx.rectangle(0, 0, (allocation.width * @value.to_f).round, allocation.height)
26
17
  @cx.fill
27
- end
18
+
19
+ if @value > 0
20
+ @cx.set_source_color(@head_color)
21
+ @cx.rectangle((allocation.width * @value.to_f).round, 0, 2, allocation.height)
22
+ @cx.fill
23
+ end
24
+ }
28
25
  end
29
26
 
30
27
  def value=(v)
@@ -1,6 +1,5 @@
1
-
2
1
  module Ampv
3
2
  PACKAGE = "ampv"
4
- VERSION = "1.0.3"
3
+ VERSION = "1.1.0"
5
4
  end
6
5
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ampv
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ahoka
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-12-30 00:00:00.000000000 Z
11
+ date: 2014-01-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: gtk2
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ! '>='
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: ruby-fifo
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -47,10 +61,11 @@ extra_rdoc_files: []
47
61
  files:
48
62
  - LICENSE
49
63
  - input.conf
64
+ - lib/ampv/config.rb
50
65
  - lib/ampv/playlist.rb
51
- - lib/ampv/mpvwidget.rb
52
66
  - lib/ampv/version.rb
53
67
  - lib/ampv/progressbarwidget.rb
68
+ - lib/ampv/mpvwidget.rb
54
69
  - lib/ampv.rb
55
70
  - bin/ampv
56
71
  homepage: https://github.com/ahodesuka/ampv
@@ -72,8 +87,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
72
87
  - !ruby/object:Gem::Version
73
88
  version: '0'
74
89
  requirements:
75
- - gtk2
76
- - mpv
90
+ - mpv v0.3.x
77
91
  rubyforge_project:
78
92
  rubygems_version: 2.0.3
79
93
  signing_key: