Update
This commit is contained in:
parent
0224abf215
commit
1b1598e668
12
Gemfile
12
Gemfile
@ -1,6 +1,8 @@
|
|||||||
source "https://rubygems.org"
|
# frozen_string_literal: true
|
||||||
|
|
||||||
gem "pry"
|
source 'https://rubygems.org'
|
||||||
gem "web_gui", :path => "."
|
|
||||||
gem "dry-struct"
|
gem 'dry-events'
|
||||||
gem "dry-events"
|
gem 'dry-struct'
|
||||||
|
gem 'pry'
|
||||||
|
gem 'web_gui', path: '.'
|
||||||
|
6
Rakefile
6
Rakefile
@ -1,5 +1,7 @@
|
|||||||
require "bundler/gem_tasks"
|
# frozen_string_literal: true
|
||||||
task :default => :build
|
|
||||||
|
require 'bundler/gem_tasks'
|
||||||
|
task default: :build
|
||||||
task :geminabox do
|
task :geminabox do
|
||||||
`gem inabox -o pkg/web_gui-*.gem`
|
`gem inabox -o pkg/web_gui-*.gem`
|
||||||
`rm pkg/web_gui-*.gem`
|
`rm pkg/web_gui-*.gem`
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "bundler/setup"
|
require 'bundler/setup'
|
||||||
require "web_gui"
|
require 'web_gui'
|
||||||
|
|
||||||
# You can add fixtures and/or initialization code here to make experimenting
|
# You can add fixtures and/or initialization code here to make experimenting
|
||||||
# with your gem easier. You can also use a different console, if you like.
|
# with your gem easier. You can also use a different console, if you like.
|
||||||
|
|
||||||
# (If you use this, don't forget to add pry to your Gemfile!)
|
# (If you use this, don't forget to add pry to your Gemfile!)
|
||||||
require "pry"
|
require 'pry'
|
||||||
Pry.start
|
Pry.start
|
||||||
|
18
calc.rb
18
calc.rb
@ -1,16 +1,18 @@
|
|||||||
require "bundler/setup"
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'bundler/setup'
|
||||||
Bundler.setup
|
Bundler.setup
|
||||||
require "web_gui"
|
require 'web_gui'
|
||||||
$op = nil
|
$op = nil
|
||||||
$num1 = nil
|
$num1 = nil
|
||||||
$num2 = nil
|
$num2 = nil
|
||||||
app=WebGui::App.new("Calculator") {
|
app = WebGui::App.new('Calculator') do
|
||||||
opthash={:add=>"Add",:sub=>"Subtract",:mult=>"Multiply",:div=>"Divide"}
|
opthash = { add: 'Add', sub: 'Subtract', mult: 'Multiply', div: 'Divide' }
|
||||||
add_element(WebGui::TextField.new { |val| $num1 = val.to_f })
|
add_element(WebGui::TextField.new { |val| $num1 = val.to_f })
|
||||||
add_element(WebGui::Menu.new(opthash) { |val| $op = val })
|
add_element(WebGui::Menu.new(opthash) { |val| $op = val })
|
||||||
add_element(WebGui::TextField.new { |val| $num2 = val.to_f })
|
add_element(WebGui::TextField.new { |val| $num2 = val.to_f })
|
||||||
res = nil
|
res = nil
|
||||||
add_element(WebGui::ActionButton.new("Calculate") {
|
add_element(WebGui::ActionButton.new('Calculate') do
|
||||||
case $op
|
case $op
|
||||||
when :add
|
when :add
|
||||||
result = $num1 + $num2
|
result = $num1 + $num2
|
||||||
@ -22,7 +24,7 @@ app=WebGui::App.new("Calculator") {
|
|||||||
result = $num1.to_f / $num2
|
result = $num1.to_f / $num2
|
||||||
end
|
end
|
||||||
res.settext("Result:#{result}")
|
res.settext("Result:#{result}")
|
||||||
})
|
end)
|
||||||
res=add_element(WebGui::Text.new("Result:"))
|
res = add_element(WebGui::Text.new('Result:'))
|
||||||
}
|
end
|
||||||
app.run
|
app.run
|
||||||
|
@ -1,69 +1,71 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Object
|
class Object
|
||||||
def descendants
|
def descendants
|
||||||
ObjectSpace.each_object(::Class).select { |klass| klass < self }
|
ObjectSpace.each_object(::Class).select { |klass| klass < self }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module WebGui; end
|
module WebGui; end
|
||||||
require "dry-struct"
|
require 'dry-struct'
|
||||||
module Types
|
module Types
|
||||||
include Dry::Types.module
|
include Dry::Types.module
|
||||||
end
|
end
|
||||||
require "dry-events"
|
require 'dry-events'
|
||||||
require_relative "web_gui/element"
|
require_relative 'web_gui/element'
|
||||||
require_relative "web_gui/event_manager"
|
require_relative 'web_gui/event_manager'
|
||||||
require_relative "web_gui/app"
|
require_relative 'web_gui/app'
|
||||||
require_relative "web_gui/window"
|
require_relative 'web_gui/window'
|
||||||
require_relative "web_gui/serv"
|
require_relative 'web_gui/serv'
|
||||||
require_relative "web_gui/wsserv"
|
require_relative 'web_gui/wsserv'
|
||||||
module WebGui
|
module WebGui
|
||||||
class Text < Element
|
class Text < Element
|
||||||
attribute :text, Types::Coercible::String
|
attribute :text, Types::Coercible::String
|
||||||
|
|
||||||
def render()
|
def render
|
||||||
return "<p id=#{id}>#{text}</p>"
|
"<p id=#{id}>#{text}</p>"
|
||||||
end
|
end
|
||||||
|
|
||||||
def settext(text)
|
def settext(text)
|
||||||
$app.update("paragraph",id,text)
|
$app.update('paragraph', id, text)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class Link < Element
|
class Link < Element
|
||||||
attribute :page, Types::Symbol
|
attribute :page, Types::Symbol
|
||||||
attribute :text, Types::Coercible::String.optional
|
attribute :text, Types::Coercible::String.optional
|
||||||
def render()
|
def render
|
||||||
if text
|
if text
|
||||||
return "<a href=\"#{page}\">#{text}</a>"
|
"<a href=\"#{page}\">#{text}</a>"
|
||||||
else
|
else
|
||||||
return "<a href=\"#{page}\">#{page.to_s}</a>"
|
"<a href=\"#{page}\">#{page}</a>"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
class Button < Link
|
class Button < Link
|
||||||
@css="a.button{-webkit-appearance: button;-moz-appearance: button;appearance: button;text-decoration: none;color: initial;padding: 0px 10px;}"
|
@css = 'a.button{-webkit-appearance: button;-moz-appearance: button;appearance: button;text-decoration: none;color: initial;padding: 0px 10px;}'
|
||||||
def render()
|
def render
|
||||||
return "<a href=\"#{page}\" class=\"button\">#{text}</a>"
|
"<a href=\"#{page}\" class=\"button\">#{text}</a>"
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.css()
|
class << self
|
||||||
return @css
|
attr_reader :css
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class Image < Element
|
class Image < Element
|
||||||
attribute :name, Types::Coercible::String
|
attribute :name, Types::Coercible::String
|
||||||
def render()
|
def render
|
||||||
return "<img src=\"#{name}.jpg\"></img>"
|
"<img src=\"#{name}.jpg\"></img>"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class Video < Element
|
class Video < Element
|
||||||
attribute :name, Types::Coercible::String
|
attribute :name, Types::Coercible::String
|
||||||
|
|
||||||
def render()
|
def render
|
||||||
return "<video controls width=320 height=300><source src=\"#{name}.webm\" type=\"video/webm\"><source src=\"#{name}.mp4\" type=\"video/mp4\"></video>"
|
"<video controls width=320 height=300><source src=\"#{name}.webm\" type=\"video/webm\"><source src=\"#{name}.mp4\" type=\"video/mp4\"></video>"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -74,8 +76,8 @@ module WebGui
|
|||||||
@block = block
|
@block = block
|
||||||
end
|
end
|
||||||
|
|
||||||
def render()
|
def render
|
||||||
return "<button id=#{id}>#{text}</button>"
|
"<button id=#{id}>#{text}</button>"
|
||||||
end
|
end
|
||||||
|
|
||||||
def on_button_pushed(event)
|
def on_button_pushed(event)
|
||||||
@ -83,7 +85,6 @@ module WebGui
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
class Menu < Element
|
class Menu < Element
|
||||||
attribute :opts, Types::Hash
|
attribute :opts, Types::Hash
|
||||||
def initialize(opthash, &block)
|
def initialize(opthash, &block)
|
||||||
@ -91,13 +92,13 @@ module WebGui
|
|||||||
@block = block
|
@block = block
|
||||||
end
|
end
|
||||||
|
|
||||||
def render()
|
def render
|
||||||
html = "<select id=#{id}>"
|
html = "<select id=#{id}>"
|
||||||
opts.each do |val, text|
|
opts.each do |val, text|
|
||||||
html+="<option value=\"#{val.to_s}\">#{text}</option>"
|
html += "<option value=\"#{val}\">#{text}</option>"
|
||||||
end
|
end
|
||||||
html+="</select>"
|
html += '</select>'
|
||||||
return html
|
html
|
||||||
end
|
end
|
||||||
|
|
||||||
def on_menu_updated(event)
|
def on_menu_updated(event)
|
||||||
@ -105,14 +106,13 @@ module WebGui
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
class TextField < Element
|
class TextField < Element
|
||||||
def initialize(opthash, &block)
|
def initialize(opthash, &block)
|
||||||
super(opthash)
|
super(opthash)
|
||||||
@block = block
|
@block = block
|
||||||
end
|
end
|
||||||
|
|
||||||
def render()
|
def render
|
||||||
html = "<input id=#{id}>"
|
html = "<input id=#{id}>"
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -128,12 +128,12 @@ module WebGui
|
|||||||
@block = block
|
@block = block
|
||||||
end
|
end
|
||||||
|
|
||||||
def render()
|
def render
|
||||||
html=""
|
html = ''
|
||||||
buttons.each do |value, text|
|
buttons.each do |value, text|
|
||||||
html+="<input type=\"radio\" id=#{id} name=\"#{id}\" value=\"#{value.to_s}\">#{text}<br>"
|
html += "<input type=\"radio\" id=#{id} name=\"#{id}\" value=\"#{value}\">#{text}<br>"
|
||||||
end
|
end
|
||||||
return html
|
html
|
||||||
end
|
end
|
||||||
|
|
||||||
def on_radiobutton_updated(event)
|
def on_radiobutton_updated(event)
|
||||||
@ -141,7 +141,6 @@ module WebGui
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
class CheckBox < Element
|
class CheckBox < Element
|
||||||
attribute :boxes, Types::Coercible::Hash
|
attribute :boxes, Types::Coercible::Hash
|
||||||
def initialize(opthash, &block)
|
def initialize(opthash, &block)
|
||||||
@ -149,12 +148,12 @@ module WebGui
|
|||||||
@block = block
|
@block = block
|
||||||
end
|
end
|
||||||
|
|
||||||
def render()
|
def render
|
||||||
html=""
|
html = ''
|
||||||
boxes.each do |value, text|
|
boxes.each do |value, text|
|
||||||
html+="<input type=\"checkbox\" id=#{id} name=\"#{id}\" value=\"#{value.to_s}\">#{text}<br>"
|
html += "<input type=\"checkbox\" id=#{id} name=\"#{id}\" value=\"#{value}\">#{text}<br>"
|
||||||
end
|
end
|
||||||
return html
|
html
|
||||||
end
|
end
|
||||||
|
|
||||||
def on_checkbox_updated(event)
|
def on_checkbox_updated(event)
|
||||||
|
@ -1,26 +1,25 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
$forcenochrome = false
|
$forcenochrome = false
|
||||||
$stdout.sync = true
|
$stdout.sync = true
|
||||||
$eventmanager = EventManager.new
|
$eventmanager = EventManager.new
|
||||||
class WebGui::App
|
module WebGui
|
||||||
|
class App
|
||||||
attr_reader :windows
|
attr_reader :windows
|
||||||
|
|
||||||
def initialize(title, &block)
|
def initialize(title, &block)
|
||||||
$app = self
|
$app = self
|
||||||
if ARGV.length>0
|
$forcenochrome = true if ARGV.length.positive? && (ARGV[0] == '--forcenochrome')
|
||||||
if ARGV[0]=="--forcenochrome"
|
|
||||||
$forcenochrome=true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@windows = {}
|
@windows = {}
|
||||||
@windows[:main] = WebGui::Window.new(name: :main, title: title)
|
@windows[:main] = WebGui::Window.new(name: :main, title: title)
|
||||||
@windows[:main].instance_eval(&block)
|
@windows[:main].instance_eval(&block)
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_document(wname)
|
def render_document(wname)
|
||||||
|
|
||||||
window = windows[wname]
|
window = windows[wname]
|
||||||
html = window.render
|
html = window.render
|
||||||
css = window.render_css
|
css = window.render_css
|
||||||
doc=<<-ENDDOC
|
<<~ENDDOC
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
@ -36,36 +35,36 @@ class WebGui::App
|
|||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
ENDDOC
|
ENDDOC
|
||||||
return doc
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_window(wname, title, &block)
|
def add_window(wname, title, &block)
|
||||||
@windows[wname] = WebGui::Window.new(name: wname, title: title)
|
@windows[wname] = WebGui::Window.new(name: wname, title: title)
|
||||||
@windows[wname].instance_eval(&block)
|
@windows[wname].instance_eval(&block)
|
||||||
end
|
end
|
||||||
def run()
|
|
||||||
|
def run
|
||||||
servthread = Thread.new do
|
servthread = Thread.new do
|
||||||
WebGui::Server.server(self)
|
WebGui::Server.server(self)
|
||||||
end
|
end
|
||||||
startwsserv()
|
startwsserv
|
||||||
if File.exists? "/Applications/Google\ Chrome.app" and !$forcenochrome
|
if File.exist?("/Applications/Google\ Chrome.app") && !$forcenochrome
|
||||||
`"/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome" --app="http://localhost:2000"`
|
`"/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome" --app="http://localhost:2000"`
|
||||||
else
|
else
|
||||||
puts "Chrome is not on your system."
|
puts 'Chrome is not on your system.'
|
||||||
puts "Please install Chrome to use this framework properly."
|
puts 'Please install Chrome to use this framework properly.'
|
||||||
puts "If Chrome is installed, please make sure it is called Google Chrome.app and is in the root Applications folder."
|
puts 'If Chrome is installed, please make sure it is called Google Chrome.app and is in the root Applications folder.'
|
||||||
puts "The app will open in your default browser as a regular webpage instead."
|
puts 'The app will open in your default browser as a regular webpage instead.'
|
||||||
sleep(5)
|
sleep(5)
|
||||||
`open http://localhost:2000`
|
`open http://localhost:2000`
|
||||||
servthread.join
|
servthread.join
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def startwsserv()
|
def startwsserv
|
||||||
@serv = WebGui::WebSocketServer.new
|
@serv = WebGui::WebSocketServer.new
|
||||||
Thread.new(self, @serv) do |parent, server|
|
Thread.new(self, @serv) do |parent, server|
|
||||||
server.accept
|
server.accept
|
||||||
while true
|
loop do
|
||||||
message = server.recv
|
message = server.recv
|
||||||
if message == false
|
if message == false
|
||||||
server.accept
|
server.accept
|
||||||
@ -78,19 +77,19 @@ class WebGui::App
|
|||||||
|
|
||||||
def handlemessage(message)
|
def handlemessage(message)
|
||||||
puts "Got message #{message}"
|
puts "Got message #{message}"
|
||||||
part1,val=message.split("=")
|
part1, val = message.split('=')
|
||||||
type=part1.match /[a-zA-Z]+/.to_s
|
type = part1.match(/[a-zA-Z]+/.to_s)
|
||||||
type = type[0]
|
type = type[0]
|
||||||
id=part1.match /\d+/
|
id = part1.match(/\d+/)
|
||||||
id = id[0].to_i
|
id = id[0].to_i
|
||||||
if val
|
if val
|
||||||
case type
|
case type
|
||||||
when "menu"
|
when 'menu'
|
||||||
val = val.to_sym
|
val = val.to_sym
|
||||||
when "radiobutton"
|
when 'radiobutton'
|
||||||
val = val.to_sym
|
val = val.to_sym
|
||||||
when "checkbox"
|
when 'checkbox'
|
||||||
vals=val.split("&")
|
vals = val.split('&')
|
||||||
temp = []
|
temp = []
|
||||||
vals.each do |val|
|
vals.each do |val|
|
||||||
temp.push(val.to_sym)
|
temp.push(val.to_sym)
|
||||||
@ -108,3 +107,4 @@ class WebGui::App
|
|||||||
@serv.send("#{type}#{id}=#{val}")
|
@serv.send("#{type}#{id}=#{val}")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
$id = 0
|
$id = 0
|
||||||
$idtoel = {}
|
$idtoel = {}
|
||||||
def get_id()
|
def get_id
|
||||||
id = $id
|
id = $id
|
||||||
$id += 1
|
$id += 1
|
||||||
return id
|
id
|
||||||
end
|
end
|
||||||
class WebGui::Element < Dry::Struct
|
|
||||||
|
module WebGui
|
||||||
|
class Element < Dry::Struct
|
||||||
constructor_type :strict_with_defaults
|
constructor_type :strict_with_defaults
|
||||||
attribute :id, Types::Coercible::Int.default { get_id() }
|
attribute :id, Types::Coercible::Int.default { get_id }
|
||||||
def initialize(opthash = nil)
|
def initialize(opthash = nil)
|
||||||
if opthash
|
if opthash
|
||||||
super(opthash)
|
super(opthash)
|
||||||
@ -15,10 +19,13 @@ class WebGui::Element < Dry::Struct
|
|||||||
end
|
end
|
||||||
$idtoel[id] = self
|
$idtoel[id] = self
|
||||||
end
|
end
|
||||||
def render()
|
|
||||||
return ""
|
def render
|
||||||
|
''
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.css
|
||||||
|
@ccs
|
||||||
end
|
end
|
||||||
def self.css()
|
|
||||||
return @ccs
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class EventManager
|
class EventManager
|
||||||
include Dry::Events::Publisher[:event_manager]
|
include Dry::Events::Publisher[:event_manager]
|
||||||
register_event("button.pushed")
|
register_event('button.pushed')
|
||||||
register_event("menu.updated")
|
register_event('menu.updated')
|
||||||
register_event("textfield.updated")
|
register_event('textfield.updated')
|
||||||
register_event("checkbox.updated")
|
register_event('checkbox.updated')
|
||||||
register_event("radiobutton.updated")
|
register_event('radiobutton.updated')
|
||||||
end
|
end
|
||||||
|
@ -1,66 +1,75 @@
|
|||||||
require "socket"
|
# frozen_string_literal: true
|
||||||
class WebGui::Server
|
|
||||||
def self.sfile(file,headers,type,client,url)
|
require 'socket'
|
||||||
|
module WebGui
|
||||||
|
class Server
|
||||||
|
def self.sfile(file, headers, type, client, _url)
|
||||||
total = file.length
|
total = file.length
|
||||||
range=headers["Range"]
|
range = headers['Range']
|
||||||
if range!=nil
|
if !range.nil?
|
||||||
positions=range.split("=")[1].split("-")
|
positions = range.split('=')[1].split('-')
|
||||||
start = positions[0].to_i(10)
|
start = positions[0].to_i(10)
|
||||||
m_end=positions[1] ? positions[1].to_i(10) : total - 1;
|
m_end = positions[1] ? positions[1].to_i(10) : total - 1
|
||||||
chunksize = (m_end - start) + 1
|
chunksize = (m_end - start) + 1
|
||||||
chunk = file[start, m_end + 1]
|
chunk = file[start, m_end + 1]
|
||||||
if type=="mp4"
|
case type
|
||||||
r_headers={"Content-Range"=>"bytes #{start}-#{m_end}/#{total}","Accept-Ranges"=>"bytes","Content-Length"=>chunksize,"Content-Type"=>"video/mp4"}
|
when 'mp4'
|
||||||
elsif type=="webm"
|
r_headers = { 'Content-Range' => "bytes #{start}-#{m_end}/#{total}", 'Accept-Ranges' => 'bytes',
|
||||||
r_headers={"Content-Range"=>"bytes #{start}-#{m_end}/#{total}","Accept-Ranges"=>"bytes","Content-Length"=>chunksize,"Content-Type"=>"video/webm"}
|
'Content-Length' => chunksize, 'Content-Type' => 'video/mp4' }
|
||||||
elsif type=="mpeg"
|
when 'webm'
|
||||||
r_headers={"Content-Range"=>"bytes #{start}-#{m_end}/#{total}","Accept-Ranges"=>"bytes","Content-Length"=>chunksize,"Content-Type"=>"audio/mpeg"}
|
r_headers = { 'Content-Range' => "bytes #{start}-#{m_end}/#{total}", 'Accept-Ranges' => 'bytes',
|
||||||
elsif type=="ogg"
|
'Content-Length' => chunksize, 'Content-Type' => 'video/webm' }
|
||||||
r_headers={"Content-Range"=>"bytes #{start}-#{m_end}/#{total}","Accept-Ranges"=>"bytes","Content-Length"=>chunksize,"Content-Type"=>"audio/ogg"}
|
when 'mpeg'
|
||||||
|
r_headers = { 'Content-Range' => "bytes #{start}-#{m_end}/#{total}", 'Accept-Ranges' => 'bytes',
|
||||||
|
'Content-Length' => chunksize, 'Content-Type' => 'audio/mpeg' }
|
||||||
|
when 'ogg'
|
||||||
|
r_headers = { 'Content-Range' => "bytes #{start}-#{m_end}/#{total}", 'Accept-Ranges' => 'bytes',
|
||||||
|
'Content-Length' => chunksize, 'Content-Type' => 'audio/ogg' }
|
||||||
end
|
end
|
||||||
header=""
|
header = ''
|
||||||
r_headers.each do |key, value|
|
r_headers.each do |key, value|
|
||||||
header += "#{key}: #{value}\n"
|
header += "#{key}: #{value}\n"
|
||||||
end
|
end
|
||||||
client.puts "HTTP/1.1 206 Partial Content"
|
client.puts 'HTTP/1.1 206 Partial Content'
|
||||||
client.print "#{header}"
|
client.print header.to_s
|
||||||
client.print "\n"
|
client.print "\n"
|
||||||
client.print "#{chunk}"
|
client.print chunk.to_s
|
||||||
else
|
else
|
||||||
if type=="mp4"
|
case type
|
||||||
r_headers={"Content-Type"=>"video/mp4"}
|
when 'mp4'
|
||||||
elsif type=="webm"
|
r_headers = { 'Content-Type' => 'video/mp4' }
|
||||||
r_headers={"Content-Type"=>"video/webm"}
|
when 'webm'
|
||||||
elsif type=="mpeg"
|
r_headers = { 'Content-Type' => 'video/webm' }
|
||||||
r_headers={"Content-Type"=>"audio/mpeg"}
|
when 'mpeg'
|
||||||
elsif type=="ogg"
|
r_headers = { 'Content-Type' => 'audio/mpeg' }
|
||||||
r_headers={"Content-Type"=>"audio/ogg"}
|
when 'ogg'
|
||||||
|
r_headers = { 'Content-Type' => 'audio/ogg' }
|
||||||
end
|
end
|
||||||
header=""
|
header = ''
|
||||||
r_headers.each do |key, value|
|
r_headers.each do |key, value|
|
||||||
header += "#{key}: #{value}\n"
|
header += "#{key}: #{value}\n"
|
||||||
end
|
end
|
||||||
client.puts "HTTP/1.1 200 OK"
|
client.puts 'HTTP/1.1 200 OK'
|
||||||
client.print "#{header}"
|
client.print header.to_s
|
||||||
client.print "\n"
|
client.print "\n"
|
||||||
client.print "#{file}"
|
client.print file.to_s
|
||||||
end
|
end
|
||||||
client.close
|
client.close
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.server(app)
|
def self.server(app)
|
||||||
Thread::abort_on_exception=true
|
Thread.abort_on_exception = true
|
||||||
server = TCPServer.new(2000)
|
server = TCPServer.new(2000)
|
||||||
$debug = true
|
$debug = true
|
||||||
igfiles=["favicon.ico"]
|
igfiles = ['favicon.ico']
|
||||||
loop do
|
loop do
|
||||||
Thread.start(server.accept) do |client|
|
Thread.start(server.accept) do |client|
|
||||||
begin
|
begin
|
||||||
lines = []
|
lines = []
|
||||||
line = client.gets
|
line = client.gets
|
||||||
while line != "\r\n"
|
while line != "\r\n"
|
||||||
if line==nil
|
if line.nil?
|
||||||
lines=[""]
|
lines = ['']
|
||||||
runreq = false
|
runreq = false
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
@ -70,63 +79,61 @@ class WebGui::Server
|
|||||||
i = 0
|
i = 0
|
||||||
lines.each do |value|
|
lines.each do |value|
|
||||||
lines[i] = value.chomp
|
lines[i] = value.chomp
|
||||||
i=i+1
|
i += 1
|
||||||
end
|
end
|
||||||
temp = lines.shift
|
temp = lines.shift
|
||||||
method=temp.split(" ")[0]
|
method = temp.split(' ')[0]
|
||||||
url=temp.split(" ")[1]
|
url = temp.split(' ')[1]
|
||||||
headers = {}
|
headers = {}
|
||||||
lines.each do |value|
|
lines.each do |value|
|
||||||
temp=value.split(": ")
|
temp = value.split(': ')
|
||||||
headers[temp[0]] = temp[1]
|
headers[temp[0]] = temp[1]
|
||||||
end
|
end
|
||||||
wname=url.gsub("/","")
|
wname = url.gsub('/', '')
|
||||||
if igfiles.include? wname
|
if igfiles.include? wname
|
||||||
client.puts "HTTP/1.1 404 Not Found"
|
client.puts 'HTTP/1.1 404 Not Found'
|
||||||
client.puts "Content-Type:text/html"
|
client.puts 'Content-Type:text/html'
|
||||||
client.puts
|
client.puts
|
||||||
client.print ""
|
client.print ''
|
||||||
else
|
elsif wname.include? '.jpg'
|
||||||
if wname.include? ".jpg"
|
|
||||||
img = File.read(wname)
|
img = File.read(wname)
|
||||||
client.puts "HTTP/1.1 200 OK"
|
client.puts 'HTTP/1.1 200 OK'
|
||||||
client.puts "Content-Type:image/jpeg"
|
client.puts 'Content-Type:image/jpeg'
|
||||||
client.puts
|
client.puts
|
||||||
client.print img
|
client.print img
|
||||||
elsif wname.include? ".mp4"
|
elsif wname.include? '.mp4'
|
||||||
sfile(File.open(wname, "rb") {|io| io.read},headers,"mp4",client,url)
|
sfile(File.open(wname, 'rb', &:read), headers, 'mp4', client, url)
|
||||||
elsif wname.include? ".webm"
|
elsif wname.include? '.webm'
|
||||||
sfile(File.open(wname, "rb") {|io| io.read},headers,"webm",client,url)
|
sfile(File.open(wname, 'rb', &:read), headers, 'webm', client, url)
|
||||||
elsif wname.include? ".js"
|
elsif wname.include? '.js'
|
||||||
if wname=="main.js"
|
if wname == 'main.js'
|
||||||
jspath=`gem which web_gui`.split("/")
|
jspath = `gem which web_gui`.split('/')
|
||||||
jspath.pop
|
jspath.pop
|
||||||
jspath.push "main.js"
|
jspath.push 'main.js'
|
||||||
jspath=jspath.join("/")
|
jspath = jspath.join('/')
|
||||||
script = File.read(jspath)
|
script = File.read(jspath)
|
||||||
else
|
else
|
||||||
script = File.read(wname)
|
script = File.read(wname)
|
||||||
end
|
end
|
||||||
client.puts "HTTP/1.1 200 OK"
|
client.puts 'HTTP/1.1 200 OK'
|
||||||
client.puts "Content-Type:application/javascript"
|
client.puts 'Content-Type:application/javascript'
|
||||||
client.puts
|
client.puts
|
||||||
client.print script
|
client.print script
|
||||||
else
|
else
|
||||||
wname="main" if wname==""
|
wname = 'main' if wname == ''
|
||||||
wname = wname.to_sym
|
wname = wname.to_sym
|
||||||
doc = app.render_document(wname)
|
doc = app.render_document(wname)
|
||||||
client.puts "HTTP/1.1 200 OK"
|
client.puts 'HTTP/1.1 200 OK'
|
||||||
client.puts "Content-Type:text/html"
|
client.puts 'Content-Type:text/html'
|
||||||
client.puts
|
client.puts
|
||||||
client.print doc
|
client.print doc
|
||||||
end
|
end
|
||||||
end
|
|
||||||
client.close
|
client.close
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
puts e
|
puts e
|
||||||
puts e.backtrace
|
puts e.backtrace
|
||||||
client.puts "HTTP/1.1 500 Internal Server Error"
|
client.puts 'HTTP/1.1 500 Internal Server Error'
|
||||||
client.puts "Content-Type:text/plain"
|
client.puts 'Content-Type:text/plain'
|
||||||
client.puts
|
client.puts
|
||||||
client.puts "Server error: #{e}"
|
client.puts "Server error: #{e}"
|
||||||
client.print e.backtrace.join("\n")
|
client.print e.backtrace.join("\n")
|
||||||
@ -136,3 +143,4 @@ class WebGui::Server
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WebGui
|
module WebGui
|
||||||
VERSION = "1.0.0"
|
VERSION = '1.0.0'
|
||||||
end
|
end
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
class WebGui::Window < Dry::Struct
|
# frozen_string_literal: true
|
||||||
attribute :title, Types::Coercible::String.default("")
|
|
||||||
|
module WebGui
|
||||||
|
class Window < Dry::Struct
|
||||||
|
attribute :title, Types::Coercible::String.default('')
|
||||||
attribute :name, Types::Coercible::String
|
attribute :name, Types::Coercible::String
|
||||||
def initialize(opthash)
|
def initialize(opthash)
|
||||||
super(opthash)
|
super(opthash)
|
||||||
@ -7,28 +10,28 @@ class WebGui::Window < Dry::Struct
|
|||||||
end
|
end
|
||||||
|
|
||||||
def add_element(element)
|
def add_element(element)
|
||||||
raise ArgumentError, "The element must be of type Element." unless element.is_a? WebGui::Element
|
raise ArgumentError, 'The element must be of type Element.' unless element.is_a? WebGui::Element
|
||||||
|
|
||||||
@elements.push element
|
@elements.push element
|
||||||
$eventmanager.subscribe(element)
|
$eventmanager.subscribe(element)
|
||||||
return element
|
element
|
||||||
end
|
end
|
||||||
|
|
||||||
def render()
|
def render
|
||||||
html=""
|
html = ''
|
||||||
@elements.each do |el|
|
@elements.each do |el|
|
||||||
html+="<p>#{el.render()}</p>"
|
html += "<p>#{el.render}</p>"
|
||||||
end
|
end
|
||||||
return html
|
html
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_css()
|
def render_css
|
||||||
css=""
|
css = ''
|
||||||
WebGui::Element.descendants.each do |el|
|
WebGui::Element.descendants.each do |el|
|
||||||
elcss = el.css
|
elcss = el.css
|
||||||
if elcss!=nil
|
css += elcss unless elcss.nil?
|
||||||
css+=elcss
|
end
|
||||||
|
css
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return css
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
@ -1,18 +1,20 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'socket' # Provides TCPServer and TCPSocket classes
|
require 'socket' # Provides TCPServer and TCPSocket classes
|
||||||
require 'digest/sha1'
|
require 'digest/sha1'
|
||||||
|
|
||||||
class WebGui::WebSocketServer
|
module WebGui
|
||||||
def initialize(host="localhost",port=2345)
|
class WebSocketServer
|
||||||
|
def initialize(host = 'localhost', port = 2345)
|
||||||
@server = TCPServer.new(host, port)
|
@server = TCPServer.new(host, port)
|
||||||
end
|
end
|
||||||
|
|
||||||
def accept()
|
def accept
|
||||||
|
|
||||||
# Wait for a connection
|
# Wait for a connection
|
||||||
@socket = @server.accept
|
@socket = @server.accept
|
||||||
|
|
||||||
# Read the HTTP request. We know it's finished when we see a line with nothing but \r\n
|
# Read the HTTP request. We know it's finished when we see a line with nothing but \r\n
|
||||||
http_request = ""
|
http_request = ''
|
||||||
while (line = @socket.gets) && (line != "\r\n")
|
while (line = @socket.gets) && (line != "\r\n")
|
||||||
http_request += line
|
http_request += line
|
||||||
end
|
end
|
||||||
@ -24,49 +26,52 @@ class WebGui::WebSocketServer
|
|||||||
socket.close
|
socket.close
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
response_key = Digest::SHA1.base64digest([websocket_key, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"].join)
|
response_key = Digest::SHA1.base64digest([websocket_key, '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'].join)
|
||||||
@socket.write <<-eos
|
@socket.write <<~EOS
|
||||||
HTTP/1.1 101 Switching Protocols
|
HTTP/1.1 101 Switching Protocols
|
||||||
Upgrade: websocket
|
Upgrade: websocket
|
||||||
Connection: Upgrade
|
Connection: Upgrade
|
||||||
Sec-WebSocket-Accept: #{response_key}
|
Sec-WebSocket-Accept: #{response_key}
|
||||||
|
#{' '}
|
||||||
eos
|
EOS
|
||||||
end
|
end
|
||||||
|
|
||||||
def recv()
|
def recv
|
||||||
first_byte = @socket.getbyte
|
first_byte = @socket.getbyte
|
||||||
fin = first_byte & 0b10000000
|
fin = first_byte & 0b10000000
|
||||||
opcode = first_byte & 0b00001111
|
opcode = first_byte & 0b00001111
|
||||||
fin = fin >> 7
|
fin = fin >> 7
|
||||||
fin = (fin ? true : false)
|
fin = (fin ? true : false)
|
||||||
raise "We don't support continuations" unless fin
|
raise "We don't support continuations" unless fin
|
||||||
unless opcode == 1 or opcode == 8
|
|
||||||
raise "We only support text data and close frame"
|
unless (opcode == 1) || (opcode == 8)
|
||||||
send("",3)
|
raise 'We only support text data and close frame'
|
||||||
|
send('', 3)
|
||||||
end
|
end
|
||||||
if opcode == 1
|
case opcode
|
||||||
|
when 1
|
||||||
second_byte = @socket.getbyte
|
second_byte = @socket.getbyte
|
||||||
is_masked = second_byte & 0b10000000
|
is_masked = second_byte & 0b10000000
|
||||||
payload_size = second_byte & 0b01111111
|
payload_size = second_byte & 0b01111111
|
||||||
|
|
||||||
raise "All incoming frames should be masked according to the websocket spec" unless is_masked
|
raise 'All incoming frames should be masked according to the websocket spec' unless is_masked
|
||||||
raise "We only support payloads < 126 bytes in length" unless payload_size < 126
|
raise 'We only support payloads < 126 bytes in length' unless payload_size < 126
|
||||||
|
|
||||||
mask = 4.times.map { @socket.getbyte }
|
mask = 4.times.map { @socket.getbyte }
|
||||||
data = payload_size.times.map { @socket.getbyte }
|
data = payload_size.times.map { @socket.getbyte }
|
||||||
unmasked_data = data.each_with_index.map { |byte, i| byte ^ mask[i % 4] }
|
unmasked_data = data.each_with_index.map { |byte, i| byte ^ mask[i % 4] }
|
||||||
string=unmasked_data.pack('C*').force_encoding('utf-8')
|
unmasked_data.pack('C*').force_encoding('utf-8')
|
||||||
return string
|
|
||||||
elsif opcode == 8
|
when 8
|
||||||
send("",0)
|
send('', 0)
|
||||||
return false
|
false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def send(message, closetype = false)
|
def send(message, closetype = false)
|
||||||
if closetype
|
if closetype
|
||||||
output = [0x88, 2, 3, 0xe8 + closetype]
|
output = [0x88, 2, 3, 0xe8 + closetype]
|
||||||
@socket.write output.pack("C*")
|
@socket.write output.pack('C*')
|
||||||
@socket.close
|
@socket.close
|
||||||
else
|
else
|
||||||
output = [0x81, message.size, message]
|
output = [0x81, message.size, message]
|
||||||
@ -74,3 +79,4 @@ Sec-WebSocket-Accept: #{response_key}
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
# coding: utf-8
|
# frozen_string_literal: true
|
||||||
lib = File.expand_path("../lib", __FILE__)
|
|
||||||
|
lib = File.expand_path('lib', __dir__)
|
||||||
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
||||||
require "web_gui/version"
|
require 'web_gui/version'
|
||||||
|
|
||||||
Gem::Specification.new do |spec|
|
Gem::Specification.new do |spec|
|
||||||
spec.name="web_gui"
|
spec.name = 'web_gui'
|
||||||
spec.version = WebGui::VERSION
|
spec.version = WebGui::VERSION
|
||||||
spec.authors=["pjht"]
|
spec.authors = ['pjht']
|
||||||
spec.email=["pjht@users.noreply.github.com"]
|
spec.email = ['pjht@users.noreply.github.com']
|
||||||
spec.summary= "A GUI framework for ruby based around the web browser"
|
spec.summary = 'A GUI framework for ruby based around the web browser'
|
||||||
spec.homepage="https://github.com/pjht/web_gui"
|
spec.homepage = 'https://github.com/pjht/web_gui'
|
||||||
spec.files=["lib/web_gui.rb","lib/main.js"]+Dir.glob("lib/web_gui/*.rb")
|
spec.files = ['lib/web_gui.rb', 'lib/main.js'] + Dir.glob('lib/web_gui/*.rb')
|
||||||
spec.bindir="exe"
|
spec.bindir = 'exe'
|
||||||
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
||||||
spec.require_paths=["lib"]
|
spec.require_paths = ['lib']
|
||||||
end
|
end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user