diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ae6aa4d --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +.repl_history +build +tags +app/pixate_code.rb +resources/*.nib +resources/*.momd +resources/*.storyboardc +.DS_Store +nbproject +.redcar +#*# +*~ +*.sw[po] +.eprj +.sass-cache +.idea +.dat*.* diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..033b46c --- /dev/null +++ b/Gemfile @@ -0,0 +1,6 @@ +source 'https://rubygems.org' + +gem 'rake' +# Add your dependencies here: +gem 'everyday-menu', '>= 1.3.4' +gem 'ib' \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..c20954a --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,30 @@ +GEM + remote: https://rubygems.org/ + specs: + activesupport (3.2.18) + i18n (~> 0.6, >= 0.6.4) + multi_json (~> 1.0) + colored (1.2) + everyday-menu (1.3.4) + rm-digest + i18n (0.6.11) + ib (0.4.7) + thor (~> 0.15.4) + tilt (~> 1.4.1) + xcodeproj (~> 0.16.0) + multi_json (1.10.1) + rake (10.3.2) + rm-digest (0.0.2) + thor (0.15.4) + tilt (1.4.1) + xcodeproj (0.16.1) + activesupport (~> 3.0) + colored (~> 1.2) + +PLATFORMS + ruby + +DEPENDENCIES + everyday-menu (>= 1.3.4) + ib + rake diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..9689316 --- /dev/null +++ b/Rakefile @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +$:.unshift('/Library/RubyMotion/lib') +require 'motion/project/template/osx' + +begin + require 'bundler' + Bundler.require +rescue LoadError +# ignored +end + +Motion::Project::App.setup do |app| + app.icon = 'Icon.icns' + app.info_plist['CFBundleIconFile'] = 'Icon.icns' + app.name = 'MemoryTamer' + app.version = '0.3' + app.identifier = 'us.myepg.MemoryTamer' + app.info_plist['NSUIElement'] = 1 + app.codesign_certificate = 'Developer ID Application: Eric Henderson (SKWXXEM822)' +end diff --git a/app/app_delegate.rb b/app/app_delegate.rb new file mode 100644 index 0000000..8c2a378 --- /dev/null +++ b/app/app_delegate.rb @@ -0,0 +1,121 @@ +class AppDelegate + attr_accessor :free_display_title + + def applicationDidFinishLaunching(notification) + # buildMenu + # buildWindow + @freeing = false + MainMenu.build! + MainMenu[:statusbar].subscribe(:status_free) { |_, _| + Thread.start { free_mem_default(get_free_mem) } + }.canExecuteBlock { |_| !@freeing } + MainMenu[:statusbar].subscribe(:status_quit) { |_, _| + NSApp.terminate + } + NSUserNotificationCenter.defaultUserNotificationCenter.setDelegate(self) + @statusItem = MainMenu[:statusbar].statusItem + pth = File.expand_path('~/prefs.mtprefs') + @mem = 1024 + @pressure = 'warn' + unless File.exist?(pth) + File.open(pth, mode_string='w+') { |io| io.puts('1024|warn') } + end + begin + if File.exist?(pth) + fc = IO.read(pth).chomp + pts = fc.split(/\|/) + @pressure = pts[1] if %w(normal warn critical).include?(pts[1].chomp) + @mem = pts[0].chomp.to_i + end + rescue + # ignored + end + @dfm = @mem * 1024**2 + puts("Starting up with memory = #{@dfm}; pressure = #{@pressure}") + Thread.start { + @last_free = NSDate.date - 120 + loop do + cfm = get_free_mem + @statusItem.setTitle(format_bytes(cfm)) + # @statusItem.didChangeValueForKey('title') + # @free_display.containedObject.setTitle("#{'%.1f' % (cfm / 1024**2)} MB free") + # @free_display.containedObject.didChangeValueForKey('title') + if cfm <= @dfm && (NSDate.date - @last_free) >= 60 && !@freeing + Thread.start { free_mem_default(cfm) } + end + sleep(2) + end + } + end + + def free_mem_default(cfm) + @freeing = true + notify 'Beginning memory freeing' + free_mem(@pressure) + nfm = get_free_mem + notify "Finished freeing #{format_bytes(nfm - cfm)}" + @freeing = false + @last_free = NSDate.date + end + + def format_bytes(bytes, show_raw = false) + lg = (Math.log(bytes)/Math.log(1024)).floor.to_f + unit = %w(B KB MB GB TB)[lg] + "#{'%.2f' % (bytes.to_f / 1024.0**lg)} #{unit}#{show_raw ? " (#{bytes} B)" : ''}" + end + + def get_free_mem + vm_stat = `vm_stat` + + vm_stat = vm_stat.split("\n") + + page_size = vm_stat[0].match(/(\d+) bytes/)[1].to_i + + pages_free = vm_stat[1].match(/(\d+)/)[1].to_i + #pages_inactive = vm_stat[3].match(/(\d+)/)[1].to_i + + page_size*pages_free #+ page_size*pages_inactive + end + + def get_memory_pressure + `/usr/sbin/sysctl kern.memorystatus_vm_pressure_level`.chomp.to_i + end + + def free_mem(pressure) + cmp = get_memory_pressure + if cmp >= 4 + notify 'Memory Pressure too high! Running not a good idea.' + return + end + dmp = pressure == 'normal' ? 1 : (pressure == 'warn' ? 2 : 4) + pressure = cmp == 1 ? 'warn' : 'critical' if cmp >= dmp + IO.popen("memory_pressure -l #{pressure}") { |pipe| + pipe.sync = true + pipe.each { |l| + puts l + # if l.include?('CMD: Allocating pages') + if l.include?('Stabilizing at') + Process.kill 'SIGINT', pipe.pid + break + end + } + } + end + + def notify(msg) + notification = NSUserNotification.alloc.init + notification.title = 'MemoryTamer' + notification.informativeText = msg + notification.soundName = NSUserNotificationDefaultSoundName + NSUserNotificationCenter.defaultUserNotificationCenter.scheduleNotification(notification) + end + + # def buildWindow + # @mainWindow = NSWindow.alloc.initWithContentRect([[240, 180], [480, 360]], + # styleMask: NSTitledWindowMask|NSClosableWindowMask|NSMiniaturizableWindowMask|NSResizableWindowMask, + # backing: NSBackingStoreBuffered, + # defer: false) + # @mainWindow.title = NSBundle.mainBundle.infoDictionary['CFBundleName'] + # @mainWindow.orderFrontRegardless + # end +end diff --git a/app/menu.rb b/app/menu.rb new file mode 100644 index 0000000..662e2fc --- /dev/null +++ b/app/menu.rb @@ -0,0 +1,37 @@ +class MainMenu + extend EverydayMenu::MenuBuilder + + def self.def_items + menuItem :hide_others, 'Hide Others', preset: :hide_others + menuItem :show_all, 'Show All', preset: :show_all + menuItem :quit, 'Quit' + + menuItem :services_item, 'Services', preset: :services + + menuItem :status_free, 'Free memory now' + menuItem :status_preferences, 'Preferences' + menuItem :status_quit, 'Quit', preset: :quit + end + + def self.def_menus + mainMenu(:app, 'MemoryTamer') { + hide_others + show_all + ___ + services_item + ___ + quit + } + + statusbarMenu(:statusbar, '', status_item_icon: NSImage.imageNamed('Status'), status_item_length: NSVariableStatusItemLength) { + status_free + ___ + status_preferences + ___ + status_quit + } + end + + def_menus + def_items +end diff --git a/resources/Credits.rtf b/resources/Credits.rtf new file mode 100644 index 0000000..46576ef --- /dev/null +++ b/resources/Credits.rtf @@ -0,0 +1,29 @@ +{\rtf0\ansi{\fonttbl\f0\fswiss Helvetica;} +{\colortbl;\red255\green255\blue255;} +\paperw9840\paperh8400 +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural + +\f0\b\fs24 \cf0 Engineering: +\b0 \ + Some people\ +\ + +\b Human Interface Design: +\b0 \ + Some other people\ +\ + +\b Testing: +\b0 \ + Hopefully not nobody\ +\ + +\b Documentation: +\b0 \ + Whoever\ +\ + +\b With special thanks to: +\b0 \ + Mom\ +} diff --git a/resources/Icon.icns b/resources/Icon.icns new file mode 100644 index 0000000..675d69c Binary files /dev/null and b/resources/Icon.icns differ diff --git a/resources/Icon.png b/resources/Icon.png new file mode 100644 index 0000000..cf21151 Binary files /dev/null and b/resources/Icon.png differ diff --git a/resources/Status.png b/resources/Status.png new file mode 100644 index 0000000..ae0f4f4 Binary files /dev/null and b/resources/Status.png differ diff --git a/resources/Status@2x.png b/resources/Status@2x.png new file mode 100644 index 0000000..f42d235 Binary files /dev/null and b/resources/Status@2x.png differ diff --git a/spec/main_spec.rb b/spec/main_spec.rb new file mode 100644 index 0000000..7804654 --- /dev/null +++ b/spec/main_spec.rb @@ -0,0 +1,9 @@ +describe "Application 'MemoryTamer'" do + before do + @app = NSApplication.sharedApplication + end + + it "has one window" do + @app.windows.size.should == 1 + end +end