Google: Adding Google Plus button to your content pages

Perhaps the best documented out of the social share buttons.

It's quite straight forward if you read the API page.

A simple example with asynchronous loading would be:

<g:plusone></g:plusone>

<script type="text/javascript">
(function() {
var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
po.src = 'https://apis.google.com/js/plusone.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
})();
</script>

That's it!

If you want to configure it, the options are listed on the page under Configuration.

Detecting Plus One Click Event

This is incredibly easy compared to Facebook's API!

<g:plusone callback="googleplus_callback"></g:plusone>

Now in your Javascript function, just check if the state is "on".

function googleplus_callback(response) {
alert(response['state'] == 'on');
}

Ah, so developer friendly!

4e31e0a74eb3d

See my other posts about sharing buttons for Facebook and Twitter.

Sources

Facebook: Add "like" button to your content pages

Bloody hell this was more cryptic than it should have been. You have two options.

Easy way

First option is the deprecated (but still used on YouTube) "share" button.

You simply just create a link in a new window like:

<a rel="nofollow" href=http://www.facebook.com/sharer.php?u=encodedURL&t=encodedTitle">Facebook</a>

It's easy because you can style it whatever way you want.

Drawback is you can't display the number of shares/likes.

Hard way

OK, don't say I didn't warn you.

  • Sign up for a Facebook developers account.
  • Once you're done, click on "Create new App" (umm yeah "App", right... wtf?)
  • When you fill in the "App Domain", be sure to tick "Website" below and fill that in too. It's stupid, I know. Otherwise you'll get an error:

Error
You have specified an App Domain but have not specified a Site URL or a Mobile Web URL

thatawesomeshirt.com must be derived from your Site URL or your Mobile Web URL.

  • Now in your page HTML, add "og" and "fb" namespaces:
<html
lang="en" xml:lang="en"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:og="http://ogp.me/ns#"
xmlns:fb="http://www.facebook.com/2008/fbml">
  • In the HTML <head> tag, add in the following properties:
<meta property="og:title" content="That Awesome Shirt!" />
<meta property="og:description" content="The best shirts from a collection of online stores rolled into one site for you to browse, add to your wishlist and rate." />
<meta property="og:image" content="http://www.thatawesomeshirt.com/media/images/logo75.png"/>
<meta property="og:url" content="http://www.thatawesomeshirt.com/ "/>
<meta property="og:type" content="blog" />
<meta property="og:site_name" content="That Awesome Shirt" />
<meta property="fb:admins" content="00000xxx" />
<meta property="fb:app_id" content="00000xxx" />
  • Remember to change fb:admins and fb:app_id to your IDs. It's on the app dashboard.
  • Man, what a face full of crap. Good thing is the OpenGraph meta tags seem to be used by a bunch of other services now so the hard work won't be only for Facebook.
  • Bad news is when you realise all those tags change dynamically on each page...
  • Finally, to add the "Like" button... go to the Like Button page and generate the HTML.
  • Paste the script at the top of the <body> tag.
  • Paste the <div> where you want the share button to appear.
  • Finally, you're done.

Extra reading

If you want to detect when the user has Liked or Un-Liked your content, you can modify the script a little.

<script type="text/javascript">
function fb_callback(add_or_remove) {
alert(add_or_remove);
}

// This allows you to detect when the button is clicked.
window.fbAsyncInit = function() {
FB.Event.subscribe('edge.create', function(response) {
fb_callback(true);
});

FB.Event.subscribe('edge.remove', function(response) {
fb_callback(false);
});
};

// Normal loading of the SDK asynchronously below
(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/all.js#xfbml=1&appId=00000xxx";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
</script>

2Yq2Q

Facebook, making you do things the hard way.

See my other posts about sharing buttons for Twitter and GooglePlus.

Sources

Twitter: Add share/tweet button on your content pages

Visit the Twitter Buttons page to generate your own.

To customise it a bit, you'll have to get your hands a little dirty.

Taking this for example:

<a href="https://twitter.com/share" class="twitter-share-button" data-url="http://twigstechtips.blogspot.com" data-text="I found this stupid post" data-via="myawesomeshirt" data-hashtags="shirts">Tweet</a>

<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>

The first line is where your options go. A simple <a> tag which the Twitter API detects and replaces with a button.

If you don't declare a certain variable, it will simply revert to the default.

  • data-url is the URL you wish to share. By default it will share the current page URL.
  • data-text is the text which will be tweeted along with the link. By default, this is the page title.
  • data-via is who you retweeted from I suppose? I set this to the site's twitter account. Default is empty.
  • data-hashtags are the hashtags you want to append to the end of the tweet.

The second line asynchronously loads the Twitter API script. This is important because it won't halt the rest of your page from loading.

See my other posts about sharing buttons for Facebook and GooglePlus.

CSS: Use display inline-block on Internet Explorer 6 and 7

IE6 has a pretty bad reputation for being incompatible with standards, and these days IE7 is the new IE6.

One of the biggest gripes I have about it is the inability to use display inline-block! It's the saviour of all layout rendering issues, yet shamed by IE.

Well, luckily there's a "compatibility hack" for it.

.shirt-details .tabs {
font-weight: bold;
font-size: larger;
display: inline-block;
/* IE6/7 */
*display: inline;
padding: 3px 10px;
height: 20px;
}

Whalla, problem solved.

uFCzAEven Christian Bale deliciously agrees.

Source

Opera: Using Firefox style keyboard shortcuts in Opera

Getting sick of Firefox and their stupid rolling releases. Even more so with the speed and memory usage. Switching between browsers hasn't really been a problem for me when it comes to vanilla installs, except for Opera.
What the heck is that Ctrl+K shortcut? Most browsers seem to focus to the search or address controls when it's pressed. Opera on the other hand goes to some weird profiles thing.
To make it more like Firefox:
  • Just open up your Opera browser and click/paste this link.
  • When a dialog shows up asking if you want to use the keyboard layout, click "Yes".
image
  • That's it! New settings already applied.
If you want to go all out and make it look and feel like Firefox, see the source link below.

*update 23/08/2012* Updated the download link for the settings file.

Source

Django: Displaying form errors under a certain field during clean()

For the most part, the forms class works pretty well. Unless you're trying to specify exactly where an error should show up in the clean() function...

If you're using fieldname_clean() methods, then this isn't a problem.

However, during your clean() method you find that it makes more sense to target an error towards a certain field, you can't do it by simply raising a forms.ValidationError.

Luckily, it's still relatively simple to do.

def clean(self):
data = self.cleaned_data

# This error shows up under form.non_field_errors
try:
shirt = Shirt.objects.get(title__iexact = data['title'])
raise forms.ValidationError("A shirt with a similar name already exists.")
except Shirt.DoesNotExist:
pass

# Show an error under the "description" field
self._errors["description"] = self.error_class(forms.ValidationError("error message of your choice).messages)

return data

Now you're able to specify error messages to any field in the form.

xf2srb
<3 Django

Windows 7: Downloading Service Pack 1

My my, so many damn files to choose from for SP1.

image

Take a look for yourself!

Files of interest

  • 7601.17514.101119-1850_Update_Sp_Wave1-GRMSP1.1_DVD.iso

The first one contains the whole SP1 she-bang for 32bit, 64bit AMD and 64bit Intel chipsets. It assumes that you're going to be using this DVD multiple times on various machines.

  • windows6.1-KB976932-IA64.exe
  • windows6.1-KB976932-X64.exe
  • windows6.1-KB976932-X86.exe

The last 3 are the specific files for each chipset architecture. This saves you from downloading the packs you don't need.

  • Windows_Win7SP1.7601.17514.101119-1850.AMD64CHK.Symbols.msi
  • Windows_Win7SP1.7601.17514.101119-1850.AMD64FRE.Symbols.msi
  • Windows_Win7SP1.7601.17514.101119-1850.IA64CHK.Symbols.msi
  • Windows_Win7SP1.7601.17514.101119-1850.IA64FRE.Symbols.msi
  • Windows_Win7SP1.7601.17514.101119-1850.X86CHK.Symbols.msi
  • Windows_Win7SP1.7601.17514.101119-1850.X86FRE.Symbols.msi

The ones in between aren't terribly useful for the majority of us. It's for people who want to debug the installation process and stuff like that.

Source

Django: Send email with attachment

Usually send_mail() will suffice for most emailing functionality, but when you need to attach something, the helpful Django wrapper function won't allow it.

If you need to add attachments to your emails, you'll have to go deeper.

image

Alright, down to the nitty gritty.

from django.core.mail.message import EmailMessage

email = EmailMessage()
email.subject = "New shirt submitted"
email.body = html_message
email.from_email = "ThatAwesomeShirt! <no-reply@thatawesomeshirt.com>"
email.to = [ "somefakeaddress@hotmail.com", ]



email.attach_file("hellokitty.jpg") # Attach a file directly

# Or alternatively, if you want to attach the contents directly

file = open("hellokitty.jpg", "rb")
email.attach(filename = "hellokitty.jpg", mimetype = "image/jpeg", content = file.read())
file.close()

email.send()

And that is all folks.

Source

Django docs: Sending email

Disable auto playing video on SMH.com.au (and other Fairfax sites) without logging in

Fuck having to log in to disable autoplay of videos. That shit shouldn't be on unless you turn it on.

DogHumpsCatsHead
It's like being raped in the skull... but given a choice if you want to sign up or not.

This snippet removes the video altogether, but to be honest you don't miss it at all.

These instructions are for Firefox, but are probably similar for other browsers.

  • Install the AdBlockPlus Firefox addon.
  • Tools menu
  • AdBlockPlus
  • Filter Preferences

image

  • Custom Filters
  • Add filter
  • Paste in "smh.com.au,theage.com.au,brisbanetimes.com.au,nationaltimes.com.au,watoday.com.au,domain.com.au##div.cT-imageMultimedia" without quotes
  • Save and close. That's it!

image 

Source

Where Greasemonkey scripts have failed, a big thanks goes to Tim Bennett for posting this tip!

Python: Various ways to sort lists and tuples

I was amazed to how many ways there were to sort lists in Python. Better yet, you DON'T need to create a new function to sort things like you do in PHP.

Basic Sorting

Create a copy of the list

sorted([5, 2, 3, 1, 4])

[1, 2, 3, 4, 5]

Sort and modify the list

This method only works for lists.

a = [5, 2, 3, 1, 4]
a.sort()
a

[1, 2, 3, 4, 5]

Sort by Key

Pyhon 2.4 brings the "key" argument to sorted().

For example, we use the tuple:

student_tuples = [
('john', 'A', 15),
('jane', 'B', 12),
('dave', 'B', 10),
]
sorted(student_tuples, key=lambda student: student[2]) # sort by age

[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

Or a class:

class Student:
def __init__(self, name, grade, age):
self.name = name
self.grade = grade
self.age = age

student_objects = [
Student('john', 'A', 15),
Student('jane', 'B', 12),
Student('dave', 'B', 10),
]
sorted(student_objects, key=lambda student: student.age) # sort by age

[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

Sort by key has some helper functions called itemgetter() and attrgetter() which do what the above examples do. It also allows for multiple levels of sorting.

from operator import itemgetter, attrgetter

sorted(student_tuples, key=itemgetter(1,2))
[('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]

sorted(student_objects, key=attrgetter('grade', 'age'))
[('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]

Reverse order

Easy enough, there's a "reverse" argument in sorted() to do that.

sorted(a, reverse = True)

Source

This post was an extract of the things I found helpful from the Python wiki docs. See the link below for more information.

vim: Delete all lines matching a search pattern

A great little command that definitely saves you time!

:g/.*foo.*/d

the "d" at the end is replaceable so you can use other commands if needed.

Source

Linux: Script to backup files/folders and copy it onto another server

There's no need to glorify how a backup can save the day. If you've been hit by a critical hard drive error, you'll know what I mean.

pgIyE
They don't have a clue how it feels to be hit by a hdd error...

So without further adieu, let's begin.

Setting up automatic access:

Your source machine will need to be able to access the destination machine without requiring a password. Why? It's better than leaving your passwords laying around in the script files.

You'll need to generate the private/public key files and then allow access on the destination machine.

Open up a terminal window and do the following:

  • ssh-keygen -t rsa
  • Press Enter (default file is fine)
  • Press Enter (empty password is how it connects without needing one)
  • Press Enter (confirm empty password)
  • cat ~/.ssh/id_rsa.pub (to display the public key)
  • Copy it to your clipboard

Now to do some preparation on your destination server.

  • Log into your remote server
  • vi .ssh/authorized_keys (to edit your access list)
  • Paste the key from your clipboard into a new line at the bottom of the file
  • Save and exit

Now try "ssh remoteuser@server.com". It should be able to connect without asking for a password.

Preparing the backup script

You'll need a place to store the backups without cluttering up your home folder.

From this point forward, I will be assuming the following:

  • I'll be storing the backup archives in
    ~/backups/
  • The folder I want to back up
    /code/

A little explanation about what's about to happen.

We'll create a script called "backup.sh" which will:

  • compress the contents of /code/ into a TGZ file
  • The TGZ filename will depend on the current date (so you can have multiple archives)
  • When it's done, we use scp to copy the archive over to the remote server.
  • After it's been uploaded, we delete the file from our source machine.
  • Just to do a bit of maintenance cleaning, we'll delete all the old archives on the remote machine apart from the newest 3.

Now to make the script that'll do the magic.

  • "vi backup.sh" to create the script file and open it up. Paste in:
FILE="/home/user/backups/backup_code_`date +%Y.%m.%d`.tgz"
FOLDER="/code/"

tar czf - $FOLDER > $FILE
scp $FILE remoteuser@server.com:~/backups/code/
rm $FILE
ssh remoteuser@server.com '~/bin/clean_backups.sh'
  • "chmod +x backup.sh" to make it executable.
  • Use "cron" to schedule the script. Edit /etc/crontab to schedule the file under YOUR username (default is "root" user). Google the syntax for usage.

Keeping a certain number of backups

"clean_backups.sh" is a script on the remote machine which automatically deletes every backup file apart from the latest 3.

This step is optional, so it's up to you if you want to do it.

On the remote server, create a script in "~/bin/clean_backups.sh" and make it executable.

Paste in:

cd ~/backups/
ls -t backup_code* | sed -e '1,3d' | xargs -d '\n' rm

For testing purposes, replace "rm" at the end with "echo". It'll print out all the files it wants to delete.

Now, if a critical hard drive failure decides to show up in your face... you're ready!

pniyp

Sources

Android: How to use True Type Fonts (TTF) in your View

The stock fonts provided with Android aren't bad, but if you need to give your app/game a bit more mood then you need some custom fonts.

To embed a custom font into your app, you need to create an "assets/fonts" folder and copy your TTF file there.

In your code:

Typeface font = Typeface.createFromAsset(this.getContext().getAssets(), "fonts/Jokerman.ttf");
TextView tv = (TextView) v.findViewById(res);
tv.setTypeface(font);

zl3c8
Can't get much easier than that!

Source

Android: Displaying items in a listview

Wow, I thought I wrote something about this some time ago but I guess I didn't. Ran into this issue again when writing my Diablo II Runewords app.

image

The ListView isn't exactly the most intuitive thing to pick up and use. It's some clever work, but the whole thing is also a mess of views, adapters and layouts. A by-product of over engineering, probably second worst case after Activities.

friday_gif_collection_151
Over engineering - you only realise when it's too late. 

Overview

A listview needs an "adapter" to hold and understand how to render the information.

Each item you see in a listview is actually another "view". This view is generated and populated by the adapter.

So, you use Listview.setAdapter() to specify which adapter you want to use.

ListView lv = (ListView) findViewById(R.id.lvRunewords);
lv.setAdapter(new RunewordListAdaptor(currentActivity, parser.runewords));

Item Layout

Instead of creating the layout by hand, we just use the layout editor. In this example, create a file called "runeword.xml" and position all the TextViews accordingly.

The Adapter

Ok, it's a lot to digest at the moment but I'll run through it.

public class RunewordListAdaptor extends ArrayAdapter<Runeword> {
public RunewordListAdaptor(Context context, List<Runeword> objects) {
// R.layout.runeword is the layout used for each item.
// R.id.tvRunewordName is the TextView used to display the item text
super(context, R.layout.runeword, R.id.tvRunewordName, objects);
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
Runeword rw = this.getItem(position);
View v;

// Android reuses views where possible.
if (convertView != null) {
v = convertView;
}
else {
// Need to create a new for the new item.
LinearLayout layout = new LinearLayout(parent.getContext());
LayoutInflater layoutInflater = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
layoutInflater.inflate(R.layout.runeword, layout);

v = layout;
}

// Fill in data onto the layout
((TextView) v.findViewById(R.id.tvRunewordName)).setText(rw.name);
((TextView) v.findViewById(R.id.tvStones)).setText(Rune.listToString(rw.stones));
((TextView) v.findViewById(R.id.tvNotes)).setText(rw.notes);
((TextView) v.findViewById(R.id.tvSockets)).setText(String.valueOf(rw.sockets));

return v;
}
}

In the constructor I just called the base class ArrayAdapter constructor, giving it the name of the layout we want to use (runeword.xml) and the id of the target TextView containing the item label (R.id.tvRunewordName).

In getView(), sometimes convertView will be given to us. This is Android's way to saving us the computational time of recreating the layout for each item.

If it needs to be created, simply inflate the layout into a view we can use.

The rest of the code is just spent filling in data from the Runeword object.

Return the view when done.

PsvJe

It's clunky, seems inefficient but it works!

Built-in Adapters

There are a few built-in adapters you can use and in this instance I used the ArrayAdapter. To see what works best for you, see the ListAdapter documentation.

Source

Android: Parsing JSON data from a String

A very handy built-in class to have is JSONTokener which I used for my Diablo II Runewords app.

public RunewordParser(String data) throws JSONException {
// Parse String "data"
JSONObject json = (JSONObject) new JSONTokener(data).nextValue();

// Some primitive data types
stones = json.getString("stones");
matched = json.getInt("matched");
onlyComplete = json.getBoolean("only_complete");

// Dealing with arrays
JSONArray runewords = json.getJSONArray("runewords");
for (int i = 0; i < runewords.length(); i++) {
Runeword runeword = new Runeword(runewords.getJSONObject(i));
this.runewords.add(runeword);
}
}

That should be enough to get you started. If you've got any questions, leave a comment.

Source

SSH Port Forwarding with Putty

Wow, this was a neat little feature I never knew existed until recently.

If you're connected to SSH to a remote server, you can tunnel through firewalls and access services via your remote computer using this method.

In the PuTTY configuration:

image

  • Go to "Connection" > "SSH" > "Tunnels"
  • For source port, enter in the port number you'd normally use on the remote machine (for instance 4680)
  • Destination should be "localhost:4680".
  • Click "Add"
  • When you connect, run an instance of the remote service on port 4680.
  • On your local computer, access "localhost:4680" and it should appear as if you were looking at it remotely.

uRvck
YAY!

Source

Using port forwarding with PuTTY

Apache: Syntax error on line 33 of <conf_file>: Name duplicates previous WSGI daemon definition.

It's likely in your vhost conf file that you've got the same daemon process already defined somewhere.

WSGIDaemonProcess  twig processes=10 threads=3 display-name=%{GROUP}

Ensure that "twig" part is UNIQUE by renaming it to something more project specific.

Source

Python: Creating XML documents

It was incredibly simple to create XML structure in Python.

from xml.etree.ElementTree import Element
from xml.etree import ElementTree

# Build the XML
root = Element("products")
root.set('matches', "15")
root.set('search_query', "whatever I searched for")

for p in products:
element = Element('product')
element.set('name', p.name)
element.set('brand', p.brand)
element.set('price', "%s" % p.price) # Convert numbers to strings
root.append(element)

similar = Element('similar')
for sp in p.similar:
similar_element = Element('similar_product')
similar_element.set('id', "%s" % sp.id)
similar_element.set('name', sp.name)
similar_element.set('brand', sp.brand)
similar.append(similar_element)

element.append(similar)

return HttpResponse(ElementTree.tostring(root), content_type = "text/xml")

Source

SVN: Using another SVN repository in your project

If you're building more than one website, it's often useful to share code between the two (such as user profile information or common utility code).

An example is:

  • ProjectA
    • submoduleA
    • submoduleB
    • submoduleC
    • profiles
 
  • Project B
    • submoduleD
    • submoduleE
    • profiles

You can see that the "profiles" submodule is shared. It'd be handy to have any changes made in ProjectA to be consistent with ProjectB.

That's when you can use SVN externals.

Setup

This may look long, but it's only a couple of clicks.

  • Create a new repository for "profiles".
  • Checkout the new "profiles" repository in a new folder (outside of your project).
  • Copy the files needed for "profiles" from ProjectA into the new folder.
  • Commit code you need from ProjectA into the new "profiles" repository.
  • Make a backup of the "profiles" submodule from ProjectA (optional of course)
  • Move or delete the "profiles" code from ProjectA.
  • Using TortoiseSVN, right click on ProjectA and go to "TortoiseSVN" > "Properties".

image

  • Click "New..." and select "svn:externals" for the property name.
  • In the property value box, the syntax is "foldername repository". For this instance, we use "profiles svn://your.svnserver.com/profiles"
  • Each new line entry will be a new submodule.

image

  • For ProjectB, make a backup and move "profiles".
  • Repeat steps to set up the "svn:externals" property.
  • Update your project to get the code for profiles.
  • You now have synchronised code!
  • Do some diff checks in your code to see if there's anything in ProjectB profiles you are missing.

You can use the command line to do this also, but the syntax isn't very intuitive so it's much easier to use TortoiseSVN. If you want to use the CLI method, search for "svn propset svn:externals".

Things to Note!

When you do an update on your project, it'll automatically update the submodules to the latest version also.

In instances where you DON'T want this to happen, you should also specify a revision number in the external entry.

This can prevent lots of heartache when you do an update on the live server and pull in some unexpected changes which may break your project!

profiles -r 21 svn://your.svnserver.com/profiles

This will cap profiles to revision 21.

Gt9Tf

You have been warned!

Sources

Accessing DynDns address within LAN

Man! This has been a pain in the ass since switching routers.

After setting up a DynDNS to point to your IP (in this instance, we'll use "twig.dyndns.com"), this is the ideal workflow from the Internet.

image

  1. From outside your home network, access SSH at twig.dyndns.com.
  2. DynDNS translates the address to your IP "200.100.50.25".
  3. Client's request reaches your home router
  4. Which sees the SSH port and forwards to the machine running SSH (XServer)

But for most routers, doing this from the local area network (LAN) has been annoying to say least.

image

In this instance, we see:

  1. TwigPC requests access to SSH at "twig.dyndns.com".
  2. DynDNS translates the address to your internet IP, "200.100.50.25", which is correct.
  3. TwigPC accesses that IP.
  4. The router recognises the IP and shortcuts the NAT (network address translation) to itself (Router), rather than forwarding it to the correct device (XServer). Most cases, you'll see access refused or a request timeout.

Here lies the problem called "NAT Loopback" or "NAT Reflection".

Both my Billion 7700N and 5 year old NetComm NB4PlusW had the same issue. Switching to NetGear DGN2000 was good, it resolved the address properly but the damn thing was a nugget.

How to Fix It?

Sorry to say, I haven't found a way to fix this. It's mainly due to the router chipset itself and/or the firmware.

If you DO know of a cure, please let me know!

There are some remedies to relieve the pain, but the main options are...

Edit your hosts file

Method: Direct all traffic for "twig.dyndns.com" to a certain IP within your LAN.

Issues: This will fix the issue for most people, but doesn't really work if you need the "twig.dyndns.com" address to correctly forward depending on port (ie. remote desktop points to TwigPC and SSH points to XServer)

There are already plenty of tutorials on the Internet to show you how to do this for each operating system.

Access it via the LAN host/address

Method: This is easiest for most people. Instead of accessing SSH on "twig.dyndns.org", I just access it via "XServer" instead, bypassing the whole DynDNS issue.

Issues: You'll have to set up your connection profiles twice (once for normal use, once for local area network use).

For example, if I wanted to use Remote Desktop from my Android phone, I need to save 2 connections; one for Internet use (twig.dyndns.com), and then one for LAN use (TwigPC).

Custom Firmware

There are a few alternatives if you're lucky enough to have a router chipset which is supported by a CFW community.

Make sure you check compatibility before doing this!

badcrab
Sorry.

Sources

 
Copyright © Twig's Tech Tips
Theme by BloggerThemes & TopWPThemes Sponsored by iBlogtoBlog