Skip to content

Instantly share code, notes, and snippets.

@knugie
Last active April 5, 2018 11:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save knugie/4999c3756cc317a2771bda369cbded01 to your computer and use it in GitHub Desktop.
Save knugie/4999c3756cc317a2771bda369cbded01 to your computer and use it in GitHub Desktop.
#!/usr/bin/env ruby
###################################################
## Display screen dimensions in macOS using ruby ##
##################################################
# found https://developer.apple.com/reference/coregraphics/1456395-cgdisplaybounds?language=objc
# --> use CoreGraphics framework
# --> CGRect CGDisplayBounds(CGDirectDisplayID display);
#############################################
## How to bind (objective)C code in ruby ? ##
#############################################
# found https://github.com/ffi/ffi/wiki
# --> gem install ffi
require 'ffi'
module Display
extend FFI::Library
######################################
## How to find CoreGraphics dynlib? ##
######################################
# --> it's in "/System/Library/Frameworks" ;-)
ffi_lib '/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics'
###########################################################
## How to find and bind (objective)C functions in ruby ? ##
###########################################################
# 1. $ nm
# --> nm -gU '/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics'
# --> not really helpful :-(
# 2. xml
# --> "/System/Library/Frameworks/CoreGraphics.framework/Versions/A/Resources/BridgeSupport/CoreGraphics.bridgesupport"
# <function name='CGMainDisplayID'>
# <retval type='I'/>
# </function>
# What is 'I' ?
# 1. https://github.com/ffi/ffi/wiki/Types / xml / dtd ...
# --> not really helpful :-(
# 2. https://github.com/patrickhno/cocoa/blob/master/lib/cocoa/objc/method_def.rb#L5
# It's :uint
###################################
## Use cocoa in the first place? ##
###################################
# --> tried, turns out there's a bug when using CGDirectDisplayID :-(
# require 'cocoa'
# Cocoa.CGMainDisplayID
# --> ArgumentError: wrong number of arguments (given 1, expected 2)
# Cocoa.CGMainDisplayID(0)
# --> ArgumentError: wrong number of arguments (given 1, expected 0)
# However, when falling back to default CGDirectDisplayID, Cocoa.CGDisplayBounds(0)
# Cocoa.CGDisplayBounds(0)
# --> <Cocoa::CGRect:0x007fa118b39078>
# I don'd want to use unmaintained gems anyway (https://github.com/patrickhno/cocoa/commits/master)
# What was 'I' again...?
# CGRect CGDisplayBounds(CGDirectDisplayID display);
# Define a new type from :uint called :CGDirectDisplayID
typedef :uint, :CGDirectDisplayID
###################################################
## How to bind (objective)C functions using ffi? ##
###################################################
# attach_function
# --> http://www.rubydoc.info/github/ffi/ffi/FFI/Library:attach_function
# --> https://github.com/ffi/ffi/blob/master/lib/ffi/library.rb#L204
# <function name='CGMainDisplayID'>
# <retval type='I'/>
# </function>
attach_function :CGMainDisplayID, [], :CGDirectDisplayID
# Display.CGMainDisplayID
# --> 69732928
# weird number, but it seems to work
# CGRect CGDisplayBounds(CGDirectDisplayID display);
###########################
## How to define CGRect? ##
###########################
# --> <struct name="CGRect"
# type="{CGRect="origin"{CGPoint="x"f"y"f}"size"{CGSize="width"f"height"f}}"
# type64="{CGRect="origin"{CGPoint="x"d"y"d}"size"{CGSize="width"d"height"d}}"/>
######################
## What's all this? ##
######################
# --> "name" is prefixed in quotes
# --> {} defined a struct(ure)
# --> f is :float
# --> d is :double
# CGPoint and CGSize need to be defined first
##################################
## How to define those structs? ##
##################################
# --> use FFI::Struct
# https://github.com/ffi/ffi/wiki/Basic-Usage#passing-structs-as-value-parameters-to-functions
# http://www.rubydoc.info/github/ffi/ffi/FFI/Struct
# <struct name="CGPoint" type="{CGPoint="x"f"y"f}" type64="{CGPoint="x"d"y"d}"/>
class CGPoint < FFI::Struct
layout :x, :double, :y, :double
end
# <struct name="CGSize" type="{CGSize="width"f"height"f}" type64="{CGSize="width"d"height"d}"/>
class CGSize < FFI::Struct
layout :width, :double, :height, :double
end
# <struct name="CGRect" type="{CGRect="origin"{CGPoint="x"f"y"f}"size"{CGSize="width"f"height"f}}" type64="{CGRect="origin"{CGPoint="x"d"y"d}"size"{CGSize="width"d"height"d}}"/>
class CGRect < FFI::Struct
layout :origin, CGPoint, :size, CGSize
end
# finally bind CGDisplayBounds
# <function name='CGDisplayBounds'>
# <arg type='I'/>
# <retval type='{CGRect={CGPoint=ff}{CGSize=ff}}' type64='{CGRect={CGPoint=dd}{CGSize=dd}}'/>
# </function>
attach_function :CGDisplayBounds, [:CGDirectDisplayID], CGRect.by_value
# Done!
end
displayID = Display.CGMainDisplayID
bounds = Display.CGDisplayBounds(displayID)
puts "width: #{'%d' % bounds[:size][:width]}, height: #{'%d' % bounds[:size][:height]}"
# There are helper methods like CGRectGetHeight, so you could spare the definition of CGSize.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment